【问题标题】:Javascript button onclick event to call function inside function in moduleJavascript按钮onclick事件调用模块中函数内部的函数
【发布时间】:2020-12-16 03:02:36
【问题描述】:

我正在使用 D3.js 根据数据绘制和着色线条,并希望在单击按钮时更新它们的颜色。 我的问题是:如何从index.html 中的按钮之一的onclick 事件中调用drawLines.js 中的函数drawLines 中声明的colorP1()colorP2()

我试过了:

  1. 使用window.drawLines = drawLines 技巧并让onclick 事件引用window.drawLines.colorP2(),但我得到Uncaught TypeError: colorP2 is not a function
  2. 使用window.colorP2 = colorP2,但我不知道在这种情况下导入将如何工作

有什么想法可以启发这个卑微的初学者的思想吗?据我了解,colorP1()colorP2() 必须留在 drawLines() 内部,因为他们需要来自 drawLines()datalines 变量——在这里随时证明我错了。

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

【问题讨论】:

    标签: javascript html d3.js


    【解决方案1】:

    你的假设是错误的:

    据我了解,colorP1() 和 colorP2() 必须留在 drawLines() 中,因为它们需要 drawLines() 中的数据和线条变量

    D3 将数据绑定到使用.data(data).join().data(data).enter() 输入的元素。基准附加到节点。使用.attr("something",function(d) { 时,d 指的是绑定数据,而不是原始数据数组。因此,您不需要原始数据数组,它是 DOM 元素的一部分。

    另外,您不需要lines,因为您可以重新选择:d3.selectAll("paths")d3.selectAll(".line")

    因此,您可以将 p1/p2 函数移到 drawLines 函数之外。

    因为我想简化下面的 sn-p,我有一个函数,它传递数据来绘制一些圆圈。然后我将事件侦听器分配给按钮(我也可以直接在按钮上使用onclick="" 属性)和 D3 来调用重新着色圆圈的函数:

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

    该函数访问绑定数据和给定属性 (d=&gt;d.color1),通过使用d3.selectAll(),我们可以选择单击时存在的所有圆圈:

    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=>d.x)
         .attr("cy",d=>d.y)
         .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>

    如果您需要数据数组本身,可以使用 d3.selectAll("elements").data()

    提取它

    当然,我们也可以在 drawLines 函数中附加按钮,这可能会产生更清晰的结果,尤其是当按钮依赖于任何形式的数据时。这样,如果您想更改按钮或功能,一切都在一个地方,例如:

    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>

    【讨论】:

    • 再次感谢,现在一切都清楚了。一个后续问题:考虑到我需要在单击按钮时通过数据对配色方案进行标准化(参见let myColor = colorInterpolate(data, 'p2'); 行),如果没有data,我将如何进行这种标准化?你会建议从 DOM 本身中提取它吗?
    • @BernardoTrindade 正在更新答案,但实际上您可以使用,您可以使用 d3.selectAll("elements").data() 重新创建数据数组以获取数据。
    【解决方案2】:

    你可以这样调用内部函数:

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

    【讨论】:

    • 感谢及时回复,onclick事件正在调用该函数。现在的问题出现在 drawLines.js 的第 2 行,因为 Cannot read property 'features' of undefined 是未定义的。鉴于 colorP2() 是在 drawLines() 中定义的,它不应该具有 data 的值吗?
    • 是的,在这种情况下数据将是未定义的,因为每次调用 drawlines() 都会调用它来创建一个新实例。在这种情况下,您的设计似乎不正确。我能想到的修改当前代码的一件事是最初将数据存储在一个全局变量中,并且每次都在 drawLines() 中使用它。在 d3 中实现的正确方法是选择 id (selectAll) 并使用填充来更改颜色。现在,color1() 和 color2() 将不在 drawLines() 函数中。
    • 我明白了,这是有道理的,您提到的第二个选项听起来更清晰。也就是说,如果没有data,你将如何规范化颜色图(let myColor = colorInterpolate(data, 'p2');)?如果可能的话,我希望没有全局变量。
    • 您将像这样调用 drawLines():d3.json("test.geojson").then((err, data) =&gt; drawLines(data)); 现在 drawLines 将有数据。现在您可以将数据传递给上述行中的colorInterpolate
    【解决方案3】:

    以及继续工作的工作示例...

    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>

    【讨论】:

    • 感谢您的代码,但我不确定我是否遵循。 javascript代码在哪里?我会将它插入一个新的 标签中吗?
    • 是的,所有代码都插入到 标记中,或者如果代码很大,则只在标记中插入链接,例如:&lt;script src="/js/script1.js"&gt;&lt;/script&gt;
    猜你喜欢
    • 2015-04-27
    • 1970-01-01
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-24
    相关资源
    最近更新 更多