简体   繁体   中英

Javascript button onclick event to call function inside function in module

I'm drawing and coloring lines based on data with D3.js and want to update their colors when clicking a button. My question is: how can I call colorP1() and colorP2() , declared in function drawLines in drawLines.js , from the onclick event of one of the buttons in index.html ?

I have tried:

  1. using the window.drawLines = drawLines trick and have the onclick event refer to window.drawLines.colorP2() , but I get Uncaught TypeError: colorP2 is not a function
  2. using window.colorP2 = colorP2 , but I don't know how the import would work in this case

Any ideas to enlighten the mind of this humble beginner? As I understand it, colorP1() and colorP2() have to stay inside drawLines() because they need the data and lines variables from drawLines() --feel free to prove me wrong here.

index.html

<html>
<head>
<style>
.line {
    stroke-width: 4px;
    fill: none;
}
</style>
</head>

<script src="https://d3js.org/d3.v6.min.js"></script>
<script type="module">
  import {drawLines} from './drawLines.js';

  d3.json("test.geojson").then(drawLines);
</script>

<body>
    <svg id='map'></svg>
    <button onclick="colorP1()">colorP1</button>
    <button onclick="colorP2()">colorP2</button>

</body>
</html>

drawLines.js

function colorInterpolate(data, property) {
    let max_d = d3.max(data.features.map(d => d.properties[property]));
    let range = [max_d, 1];
    return d3.scaleSequential().domain(range).interpolator(d3.interpolateViridis); 
}

export function drawLines(data) {

    let width = 900,
        height = 500,
        initialScale = 1 << 23,
        initialCenter = [-74.200698022608137, 40.034504451003734]

    let svg = d3.select('#map')
        .attr('height', height)
        .attr('width', width)

    let projection = d3.geoMercator()
        .scale(initialScale)
        .center(initialCenter)
        .translate([width / 2, height / 2])

    let path = d3.geoPath(projection)

    let myColor = colorInterpolate(data, 'p1');

    let lines = svg.append('g')

    lines.selectAll('path')
        .data(data.features)
        .join('path')
        .attr('class', 'line')
        .attr('d', path)
        .attr("stroke", function(d) {
                return myColor(d.properties.p1);
            })

    function colorP2() {
        let myColor = colorInterpolate(data, 'p2');
        lines.selectAll('path')
            .attr("stroke", d => myColor(d.properties.p2))
    }

    function colorP1() {
        let myColor = colorInterpolate(data, 'p1');
        lines.selectAll('path')
            .attr("stroke", d => myColor(d.properties.p1))
    }
}

test.geojson

{
"type": "FeatureCollection",
"name": "lines",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "id": 3, "p1": 1, "p2": 3}, "geometry": { "type": "LineString", "coordinates": [ [ -74.201304101157845, 40.033790926216739 ], [ -74.201226425025339, 40.033761910802717 ], [ -74.201164135201353, 40.033738641825124 ] ] } },
{ "type": "Feature", "properties": { "id": 4, "p1": 2, "p2": 2}, "geometry": { "type": "LineString", "coordinates": [ [ -74.200521185229846, 40.034804885753857 ], [ -74.200535458528648, 40.034780636493231 ], [ -74.200698022608137, 40.034504451003734 ], [ -74.200932444446437, 40.034106179618831 ], [ -74.201017665586349, 40.033961391736824 ] ] } }
]
}

You can call like this to call inner functions:

<button onclick="(new drawLines().colorP1())">colorP1</button>
<button onclick="(new drawLines().colorP2())">colorP2</button>

Your assumption is wrong:

As I understand it, colorP1() and colorP2() have to stay inside drawLines() because they need the data and lines variables from drawLines()

D3 binds data to the elements entered with .data(data).join() or .data(data).enter() . The datum is attached to the node. When using .attr("something",function(d) { the d refers to the bound datum, not the original data array. So, you don't need the original data array, it is part of the DOM element.

Also, you don't need lines because you can remake that selection: d3.selectAll("paths") or d3.selectAll(".line") .

So, you can move the p1/p2 functions outside of your drawLines function.

As I wanted to simplify for the snippet below, I've got a function that is passed data to draw some circles. I then assign event listeners to the buttons (I could also use onclick="" attributes on the buttons directly) with D3 to call functions that recolor the circles:

function color1() {
 d3.selectAll("circle")
   .attr("fill",d=>d.color1);
}

The function access the bound datum and a given property ( d=>d.color1 ) and by using d3.selectAll() we can select all the circles that exist at the time of the click:

 function draw(data) { var svg = d3.select("body").append("svg").attr("width", 300).attr("height", 200); svg.selectAll("circle").data(data).enter().append("circle").attr("cx",d=>dx).attr("cy",d=>dy).attr("fill",d=>d.color2).attr("r", 20); } draw([{x: 100,y:50, color1: "steelblue",color2:"crimson"},{x:200,y:50,color1:"steelblue",color2:"crimson"}]) d3.selectAll("button").data([0,1]).on("click", function(event,d) { if (d) color2(); else color1(); }) function color1() { d3.selectAll("circle").attr("fill",d=>d.color1); } function color2() { d3.selectAll("circle").attr("fill",d=>d.color2); }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script> <div> <button> Blue </button> <button> Red </button> </div>

If you need the data array itself, you can extract that with d3.selectAll("elements").data()

Of course, we could also append the buttons in your drawLines function, which would potentially make a cleaner outcome, especially if the buttons are dependent on the data in any form. This way if you ever wanted to change the buttons or the functions, everything is in one place, for example:

 var geojson = { "type": "FeatureCollection","name": "lines","crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },"features": [{ "type": "Feature", "properties": { "id": 3, "p1": 1, "p2": 3}, "geometry": { "type": "LineString", "coordinates": [ [ -74.201304101157845, 40.033790926216739 ], [ -74.201226425025339, 40.033761910802717 ], [ -74.201164135201353, 40.033738641825124 ] ] } },{ "type": "Feature", "properties": { "id": 4, "p1": 2, "p2": 2}, "geometry": { "type": "LineString", "coordinates": [ [ -74.200521185229846, 40.034804885753857 ], [ -74.200535458528648, 40.034780636493231 ], [ -74.200698022608137, 40.034504451003734 ], [ -74.200932444446437, 40.034106179618831 ], [ -74.201017665586349, 40.033961391736824 ] ] } }]}; function drawLines(data) { let width = 500, height = 400, initialScale = 1 << 23, initialCenter = [-74.200698022608137, 40.034504451003734] let svg = d3.select('#map').attr('height', height).attr('width', width) let projection = d3.geoMercator().fitSize([width,height],data) let path = d3.geoPath(projection) let myColor = colorInterpolate(data, 'p1'); let lines = svg.append('g') lines.selectAll('path').data(data.features).join('path').attr('class', 'line').attr('d', path) colorBy("p1"); function colorBy(property) { let myColor = colorInterpolate(property); lines.selectAll('path').attr("stroke", d => myColor(d.properties[property])) } function colorInterpolate(property) { let max_d = d3.max(data.features.map(d => d.properties[property])); let range = [max_d, 1]; return d3.scaleSequential().domain(range).interpolator(d3.interpolateViridis); } d3.selectAll(".property").data(["p1","p2"]).enter().append("button").attr("class","property").text(d=>d).on("click", function(_,d) { colorBy(d); }).lower(); } drawLines(geojson);
 .line { stroke-width: 4px; fill: none; }
 <script src="https://d3js.org/d3.v6.min.js"></script> <svg id='map'></svg>

and work example for continue working...

 var json1 ='{ "type": "FeatureCollection", "name":"lines", "crs": { "type": "name", "properties":{ "name":"urn:ogc:def:crs:OGC:1.3:CRS84" }}, "features": [{ "type": "Feature", "properties": { "id": 3, "p1": 1, "p2": 3}, "geometry": {"type": "LineString","coordinates":[[ -74.201304101157845, 40.033790926216739],[-74.201226425025339,40.033761910802717 ],[-74.201164135201353,40.033738641825124]]}},{"type": "Feature","properties":{ "id": 4, "p1": 2, "p2":2 },"geometry": { "type": "LineString", "coordinates": [[ -74.200521185229846, 40.034804885753857 ],[ -74.200535458528648, 40.034780636493231 ],[ -74.200698022608137, 40.034504451003734 ],[ -74.200932444446437, 40.034106179618831 ],[ -74.201017665586349, 40.033961391736824 ]]}}]}'; var width = 900, height = 500, initialScale = 1 << 23, initialCenter = [-74.198698022608137, 40.034504451003734] var svg = d3.select('#map').attr('height', height).attr('width', width); var lines = svg.append('g'); var projection = d3.geoMercator().scale(initialScale).center(initialCenter).translate([width / 2, height / 2]) var path = d3.geoPath(projection) function colorInterpolate(data, property) { let max_d = d3.max(data.features.map(d => d.properties[property])); let range = [max_d, 1]; return d3.scaleSequential().domain(range).interpolator(d3.interpolateViridis); } function drawLines(data) { let myColor = colorInterpolate(data, 'p1'); lines.selectAll('path').data(data.features).join('path').attr('class', 'line').attr('d', path).attr("stroke", function(d) { return myColor(d.properties.p1); }); } function colorP2(data){ let myColor = colorInterpolate(data, 'p2'); lines.selectAll('path').attr("stroke", d=>myColor(d.properties.p2)); } function colorP1(data){ let myColor = colorInterpolate(data, 'p1'); lines.selectAll('path').attr("stroke", d=>myColor(d.properties.p1)); }
 <html> <head> <style>.line { stroke-width: 4px; fill: none; } </style> </head> <script src="https://d3js.org/d3.v6.min.js"></script> <script type="module"> //import {drawLines} from './drawLines.js'; //d3.json("test.geojson").then(drawLines); drawLines(JSON.parse(json1)); </script> <body> <svg id='map'></svg> <button onclick="colorP1(JSON.parse(json1))">colorP1</button> <button onclick="colorP2(JSON.parse(json1))">colorP2</button> </body> </html>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM