D3 граф раскраска упаковки граф - PullRequest
1 голос
/ 10 апреля 2020

Я нарисовал масштабируемый график упаковки кругов (aka flare graph) с помощью d3. js ( версия 3.4.11 )

displayFlareGraph: function (containerId, options, json) {
        options.width = options.width || 1280; // circle width
        options.height = options.height || 800; // circle height
        options.radius = options.radius || 720; // circle radius
        var w = options.width,
            h = options.height,
            r = options.radius,
            x = d3.scale.linear().range([0, r]),
            y = d3.scale.linear().range([0, r]),
            node,
            root;

        var color = (key) => Palette.pickColor("labels_colors", key);

        var pack = d3.layout.pack()
            .size([r, r])
            .value(function (d) {
                return d.size;
            })

        var vis = d3.select(containerId).insert("svg:svg", "h2")
            .attr("width", w)
            .attr("height", h)
            .append("svg:g")
            .attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");

        function zoom(d, i) {
            var k = r / d.r / 2;
            x.domain([d.x - d.r, d.x + d.r]);
            y.domain([d.y - d.r, d.y + d.r]);

            var t = vis.transition()
                .duration(d3.event.altKey ? 7500 : 750);

            t.selectAll("circle")
                .attr("cx", function (d) {
                    return x(d.x);
                })
                .attr("cy", function (d) {
                    return y(d.y);
                })
                .attr("r", function (d) {
                    return k * d.r;
                })

            t.selectAll("text")
                .attr("x", function (d) {
                    return x(d.x);
                })
                .attr("y", function (d) {
                    return d.children ? y(d.y) : y(d.y + 10);
                })
                .style("opacity", function (d) {
                    return k * d.r > 20 ? 1 : 0;
                });

            node = d;
            d3.event.stopPropagation();
        } //zoom

        node = root = json;
        var nodes = pack.nodes(root);
        vis.selectAll("circle")
            .data(nodes)
            .enter().append("svg:circle")
            .attr("class", function (d) {
                return d.children ? "parent" : "child";
            })
            .attr("cx", function (d) {
                return d.x;
            })
            .attr("cy", function (d) {
                return d.y;
            })
            .attr("r", function (d) {
                return d.r;
            })
            .attr("fill", d => color(d.height))
            .on("click", function (d) {
                return zoom(node == d ? root : d);
            });

        vis.selectAll("text")
            .data(nodes)
            .enter().append("svg:text")
            .attr("class", function (d) {
                return d.children ? "parent" : "child";
            })
            .attr("x", function (d) {
                return d.x;
            })
            .attr("y", function (d) {
                return d.children ? d.y : d.y + 10;
            })
            .attr("dy", ".35em")
            .attr("text-anchor", "middle")
            .style("opacity", function (d) {
                return d.r > 20 ? 1 : 0;
            })
            .text(function (d) {
                return d.name;
            });

        d3.select(window).on("click", function () {
            zoom(root);
        });

    } //displayFlareGraph

Я хочу раскрасить круги сейчас и я попытался использовать свойство fill, подобное этому

.attr("r", function (d) {
    return d.r;
})
.attr("fill", d => color(d.height))

, где моя функция color возвращает случайный цвет RGB. У меня по умолчанию CSS, который делает цвета кругов. Но fill, похоже, не отменяет эти настройки.

var displayFlareGraph = function(containerId, options, json) {
  options.width = options.width || 1280; // circle width
  options.height = options.height || 800; // circle height
  options.radius = options.radius || 720; // circle radius
  var w = options.width,
    h = options.height,
    r = options.radius,
    x = d3.scale.linear().range([0, r]),
    y = d3.scale.linear().range([0, r]),
    node,
    root;

  var color = (key) => {
    return {
               "r": ~~(Math.random() * 255), 
               "g": ~~(Math.random() * 255), 
               "b": ~~(Math.random() * 255),
               "opacity": 0.9
         }
  }

  var pack = d3.layout.pack()
    .size([r, r])
    .value(function(d) {
      return d.size;
    })

  var vis = d3.select(containerId).insert("svg:svg", "h2")
    .attr("width", w)
    .attr("height", h)
    .append("svg:g")
    .attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");

  function zoom(d, i) {
    var k = r / d.r / 2;
    x.domain([d.x - d.r, d.x + d.r]);
    y.domain([d.y - d.r, d.y + d.r]);

    var t = vis.transition()
      .duration(d3.event.altKey ? 7500 : 750);

    t.selectAll("circle")
      .attr("cx", function(d) {
        return x(d.x);
      })
      .attr("cy", function(d) {
        return y(d.y);
      })
      .attr("r", function(d) {
        return k * d.r;
      })

    t.selectAll("text")
      .attr("x", function(d) {
        return x(d.x);
      })
      .attr("y", function(d) {
        return d.children ? y(d.y) : y(d.y + 10);
      })
      .style("opacity", function(d) {
        return k * d.r > 20 ? 1 : 0;
      });

    node = d;
    d3.event.stopPropagation();
  } //zoom

  node = root = json;
  var nodes = pack.nodes(root);
  vis.selectAll("circle")
    .data(nodes)
    .enter().append("svg:circle")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", function(d) {
      return d.r;
    })
    .attr("fill", d => color(d.height))
    .on("click", function(d) {
      return zoom(node == d ? root : d);
    });

  vis.selectAll("text")
    .data(nodes)
    .enter().append("svg:text")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.children ? d.y : d.y + 10;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .style("opacity", function(d) {
      return d.r > 20 ? 1 : 0;
    })
    .text(function(d) {
      return d.name;
    });

  d3.select(window).on("click", function() {
    zoom(root);
  });

} //displayFlareGraph
//https://observablehq.com/@d3/zoomable-circle-packing


$(document).ready(function() {
  displayFlareGraph('#graph', {
    width: 400,
    height: 300,
    radius: 400
  }, NODES);
})

const NODES = {
  "children": [{
    "name": "#0",
    "children": [{
      "name": "back",
      "size": 0.086
    }, {
      "name": "time",
      "size": 0.036
    }, {
      "name": "dance",
      "size": 0.036
    }, {
      "name": "make",
      "size": 0.034
    }, {
      "name": "summer",
      "size": 0.029
    }, {
      "name": "stay",
      "size": 0.029
    }, {
      "name": "white",
      "size": 0.022
    }, {
      "name": "lying",
      "size": 0.022
    }, {
      "name": "whoa",
      "size": 0.021
    }]
  }, {
    "name": "#1",
    "children": [{
      "name": "heart",
      "size": 0.086
    }, {
      "name": "broke",
      "size": 0.072
    }, {
      "name": "story",
      "size": 0.061
    }, {
      "name": "life",
      "size": 0.05
    }, {
      "name": "give",
      "size": 0.048
    }, {
      "name": "start",
      "size": 0.025
    }, {
      "name": "time",
      "size": 0.022
    }]
  }, {
    "name": "#2",
    "children": [{
      "name": "make",
      "size": 0.087
    }, {
      "name": "beautiful",
      "size": 0.071
    }, {
      "name": "world",
      "size": 0.068
    }, {
      "name": "baby",
      "size": 0.033
    }, {
      "name": "hair",
      "size": 0.03
    }, {
      "name": "understand",
      "size": 0.029
    }, {
      "name": "light",
      "size": 0.025
    }, {
      "name": "hard",
      "size": 0.025
    }, {
      "name": "ground",
      "size": 0.024
    }, {
      "name": "smile",
      "size": 0.023
    }, {
      "name": "heads",
      "size": 0.022
    }, {
      "name": "hear",
      "size": 0.021
    }, {
      "name": "flip",
      "size": 0.021
    }, {
      "name": "overwhelmed",
      "size": 0.02
    }, {
      "name": "nana",
      "size": 0.02
    }]
  }, {
    "name": "#3",
    "children": [{
      "name": "words",
      "size": 0.044
    }, {
      "name": "tears",
      "size": 0.041
    }, {
      "name": "rock",
      "size": 0.036
    }, {
      "name": "stop",
      "size": 0.031
    }, {
      "name": "half",
      "size": 0.027
    }, {
      "name": "heart",
      "size": 0.026
    }]
  }, {
    "name": "#4",
    "children": [{
      "name": "gonna",
      "size": 0.12
    }, {
      "name": "wanna",
      "size": 0.093
    }, {
      "name": "girl",
      "size": 0.047
    }, {
      "name": "steal",
      "size": 0.042
    }, {
      "name": "meet",
      "size": 0.038
    }, {
      "name": "belongs",
      "size": 0.037
    }, {
      "name": "hold",
      "size": 0.036
    }, {
      "name": "getcha",
      "size": 0.034
    }, {
      "name": "alright",
      "size": 0.029
    }]
  }, {
    "name": "#5",
    "children": [{
      "name": "love",
      "size": 0.23
    }, {
      "name": "things",
      "size": 0.063
    }, {
      "name": "make",
      "size": 0.047
    }, {
      "name": "perfect",
      "size": 0.024
    }, {
      "name": "heart",
      "size": 0.021
    }]
  }, {
    "name": "#6",
    "children": [{
      "name": "night",
      "size": 0.14
    }, {
      "name": "song",
      "size": 0.093
    }, {
      "name": "danced",
      "size": 0.074
    }, {
      "name": "forget",
      "size": 0.069
    }, {
      "name": "remember",
      "size": 0.056
    }, {
      "name": "home",
      "size": 0.035
    }, {
      "name": "afraid",
      "size": 0.034
    }, {
      "name": "line",
      "size": 0.029
    }, {
      "name": "wildest",
      "size": 0.02
    }, {
      "name": "dreaming",
      "size": 0.02
    }]
  }, {
    "name": "#7",
    "children": [{
      "name": "feel",
      "size": 0.082
    }, {
      "name": "tonight",
      "size": 0.039
    }, {
      "name": "gonna",
      "size": 0.038
    }, {
      "name": "eyes",
      "size": 0.037
    }, {
      "name": "calls",
      "size": 0.034
    }, {
      "name": "longer",
      "size": 0.031
    }, {
      "name": "heart",
      "size": 0.031
    }, {
      "name": "saved",
      "size": 0.028
    }, {
      "name": "turning",
      "size": 0.027
    }, {
      "name": "hold",
      "size": 0.026
    }, {
      "name": "falling",
      "size": 0.025
    }, {
      "name": "love",
      "size": 0.021
    }, {
      "name": "light",
      "size": 0.02
    }]
  }, {
    "name": "#8",
    "children": [{
      "name": "baby",
      "size": 0.13
    }, {
      "name": "wanna",
      "size": 0.077
    }, {
      "name": "moving",
      "size": 0.031
    }, {
      "name": "kiss",
      "size": 0.031
    }, {
      "name": "girl",
      "size": 0.029
    }, {
      "name": "stay",
      "size": 0.026
    }, {
      "name": "feel",
      "size": 0.026
    }, {
      "name": "find",
      "size": 0.023
    }, {
      "name": "good",
      "size": 0.021
    }, {
      "name": "change",
      "size": 0.021
    }, {
      "name": "hold",
      "size": 0.02
    }]
  }, {
    "name": "#9",
    "children": [{
      "name": "forever",
      "size": 0.09
    }, {
      "name": "live",
      "size": 0.062
    }, {
      "name": "life",
      "size": 0.053
    }, {
      "name": "young",
      "size": 0.04
    }, {
      "name": "kids",
      "size": 0.03
    }, {
      "name": "happenin",
      "size": 0.022
    }]
  }]
}
body>svg {
  position: absolute;
  top: -80px;
  left: -160px;
}

text {
  font-size: 11px;
  pointer-events: none;
}

text.parent {
  fill: #1f77b4;
}

circle {
  fill: #ccc;
  stroke: #999;
  pointer-events: all;
}

circle.parent {
  fill: #1f77b4;
  fill-opacity: .1;
  stroke: steelblue;
}

circle.parent:hover {
  stroke: #ff7f0e;
  stroke-width: .5px;
}

circle.child {
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-cloud/1.2.4/d3.layout.cloud.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="graph"></div>

1 Ответ

1 голос
/ 11 апреля 2020

Существуют две проблемы, препятствующие генерированию случайных цветов.

  1. Определенный css fill имеет приоритет над fill атрибутом SVG. Поэтому вместо использования selection.attr('fill'... следует использовать selection.style('fill'.... Прочитайте этот ответ для подробностей.

  2. Функция color возвращает объект вместо цветовой строки. Это видно на панели инспектора DOM консоли разработчика. где круги имеют атрибут fill="[object object]"

Функция цвета может быть адаптирована следующим образом, чтобы возвращать строку вместо массива:

var color = () => {
    return 'rgba(' 
        + ~~(Math.random() * 255) + ',' 
        +  ~~(Math.random() * 255) + ',' 
        + ~~(Math.random() * 255) + ', .9)'
}

Примечание: Нет необходимости передавать d.key в качестве параметра функции цвета, поскольку он вообще не используется.

Демонстрация в фрагменте ниже.

var displayFlareGraph = function(containerId, options, json) {
  options.width = options.width || 1280; // circle width
  options.height = options.height || 800; // circle height
  options.radius = options.radius || 720; // circle radius
  var w = options.width,
    h = options.height,
    r = options.radius,
    x = d3.scale.linear().range([0, r]),
    y = d3.scale.linear().range([0, r]),
    node,
    root;

  var color = () => {
    return 'rgba(' + ~~(Math.random() * 255) + ',' +  ~~(Math.random() * 255) + ',' + ~~(Math.random() * 255) + ', .9)'
  }

  var pack = d3.layout.pack()
    .size([r, r])
    .value(function(d) {
      return d.size;
    })

  var vis = d3.select(containerId).insert("svg:svg", "h2")
    .attr("width", w)
    .attr("height", h)
    .append("svg:g")
    .attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");

  function zoom(d, i) {
    var k = r / d.r / 2;
    x.domain([d.x - d.r, d.x + d.r]);
    y.domain([d.y - d.r, d.y + d.r]);

    var t = vis.transition()
      .duration(d3.event.altKey ? 7500 : 750);

    t.selectAll("circle")
      .attr("cx", function(d) {
        return x(d.x);
      })
      .attr("cy", function(d) {
        return y(d.y);
      })
      .attr("r", function(d) {
        return k * d.r;
      })

    t.selectAll("text")
      .attr("x", function(d) {
        return x(d.x);
      })
      .attr("y", function(d) {
        return d.children ? y(d.y) : y(d.y + 10);
      })
      .style("opacity", function(d) {
        return k * d.r > 20 ? 1 : 0;
      });

    node = d;
    d3.event.stopPropagation();
  } //zoom

  node = root = json;
  var nodes = pack.nodes(root);
  vis.selectAll("circle")
    .data(nodes)
    .enter().append("circle")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", function(d) {
      return d.r;
    })
    .style("fill", d => color())
    .on("click", function(d) {
      return zoom(node == d ? root : d);
    });

  vis.selectAll("text")
    .data(nodes)
    .enter().append("svg:text")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.children ? d.y : d.y + 10;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .style("opacity", function(d) {
      return d.r > 20 ? 1 : 0;
    })
    .text(function(d) {
      return d.name;
    });

  d3.select(window).on("click", function() {
    zoom(root);
  });

} //displayFlareGraph
//https://observablehq.com/@d3/zoomable-circle-packing


$(document).ready(function() {
  displayFlareGraph('#graph', {
    width: 400,
    height: 300,
    radius: 400
  }, NODES);
})

const NODES = {
  "children": [{
    "name": "#0",
    "children": [{
      "name": "back",
      "size": 0.086
    }, {
      "name": "time",
      "size": 0.036
    }, {
      "name": "dance",
      "size": 0.036
    }, {
      "name": "make",
      "size": 0.034
    }, {
      "name": "summer",
      "size": 0.029
    }, {
      "name": "stay",
      "size": 0.029
    }, {
      "name": "white",
      "size": 0.022
    }, {
      "name": "lying",
      "size": 0.022
    }, {
      "name": "whoa",
      "size": 0.021
    }]
  }, {
    "name": "#1",
    "children": [{
      "name": "heart",
      "size": 0.086
    }, {
      "name": "broke",
      "size": 0.072
    }, {
      "name": "story",
      "size": 0.061
    }, {
      "name": "life",
      "size": 0.05
    }, {
      "name": "give",
      "size": 0.048
    }, {
      "name": "start",
      "size": 0.025
    }, {
      "name": "time",
      "size": 0.022
    }]
  }, {
    "name": "#2",
    "children": [{
      "name": "make",
      "size": 0.087
    }, {
      "name": "beautiful",
      "size": 0.071
    }, {
      "name": "world",
      "size": 0.068
    }, {
      "name": "baby",
      "size": 0.033
    }, {
      "name": "hair",
      "size": 0.03
    }, {
      "name": "understand",
      "size": 0.029
    }, {
      "name": "light",
      "size": 0.025
    }, {
      "name": "hard",
      "size": 0.025
    }, {
      "name": "ground",
      "size": 0.024
    }, {
      "name": "smile",
      "size": 0.023
    }, {
      "name": "heads",
      "size": 0.022
    }, {
      "name": "hear",
      "size": 0.021
    }, {
      "name": "flip",
      "size": 0.021
    }, {
      "name": "overwhelmed",
      "size": 0.02
    }, {
      "name": "nana",
      "size": 0.02
    }]
  }, {
    "name": "#3",
    "children": [{
      "name": "words",
      "size": 0.044
    }, {
      "name": "tears",
      "size": 0.041
    }, {
      "name": "rock",
      "size": 0.036
    }, {
      "name": "stop",
      "size": 0.031
    }, {
      "name": "half",
      "size": 0.027
    }, {
      "name": "heart",
      "size": 0.026
    }]
  }, {
    "name": "#4",
    "children": [{
      "name": "gonna",
      "size": 0.12
    }, {
      "name": "wanna",
      "size": 0.093
    }, {
      "name": "girl",
      "size": 0.047
    }, {
      "name": "steal",
      "size": 0.042
    }, {
      "name": "meet",
      "size": 0.038
    }, {
      "name": "belongs",
      "size": 0.037
    }, {
      "name": "hold",
      "size": 0.036
    }, {
      "name": "getcha",
      "size": 0.034
    }, {
      "name": "alright",
      "size": 0.029
    }]
  }, {
    "name": "#5",
    "children": [{
      "name": "love",
      "size": 0.23
    }, {
      "name": "things",
      "size": 0.063
    }, {
      "name": "make",
      "size": 0.047
    }, {
      "name": "perfect",
      "size": 0.024
    }, {
      "name": "heart",
      "size": 0.021
    }]
  }, {
    "name": "#6",
    "children": [{
      "name": "night",
      "size": 0.14
    }, {
      "name": "song",
      "size": 0.093
    }, {
      "name": "danced",
      "size": 0.074
    }, {
      "name": "forget",
      "size": 0.069
    }, {
      "name": "remember",
      "size": 0.056
    }, {
      "name": "home",
      "size": 0.035
    }, {
      "name": "afraid",
      "size": 0.034
    }, {
      "name": "line",
      "size": 0.029
    }, {
      "name": "wildest",
      "size": 0.02
    }, {
      "name": "dreaming",
      "size": 0.02
    }]
  }, {
    "name": "#7",
    "children": [{
      "name": "feel",
      "size": 0.082
    }, {
      "name": "tonight",
      "size": 0.039
    }, {
      "name": "gonna",
      "size": 0.038
    }, {
      "name": "eyes",
      "size": 0.037
    }, {
      "name": "calls",
      "size": 0.034
    }, {
      "name": "longer",
      "size": 0.031
    }, {
      "name": "heart",
      "size": 0.031
    }, {
      "name": "saved",
      "size": 0.028
    }, {
      "name": "turning",
      "size": 0.027
    }, {
      "name": "hold",
      "size": 0.026
    }, {
      "name": "falling",
      "size": 0.025
    }, {
      "name": "love",
      "size": 0.021
    }, {
      "name": "light",
      "size": 0.02
    }]
  }, {
    "name": "#8",
    "children": [{
      "name": "baby",
      "size": 0.13
    }, {
      "name": "wanna",
      "size": 0.077
    }, {
      "name": "moving",
      "size": 0.031
    }, {
      "name": "kiss",
      "size": 0.031
    }, {
      "name": "girl",
      "size": 0.029
    }, {
      "name": "stay",
      "size": 0.026
    }, {
      "name": "feel",
      "size": 0.026
    }, {
      "name": "find",
      "size": 0.023
    }, {
      "name": "good",
      "size": 0.021
    }, {
      "name": "change",
      "size": 0.021
    }, {
      "name": "hold",
      "size": 0.02
    }]
  }, {
    "name": "#9",
    "children": [{
      "name": "forever",
      "size": 0.09
    }, {
      "name": "live",
      "size": 0.062
    }, {
      "name": "life",
      "size": 0.053
    }, {
      "name": "young",
      "size": 0.04
    }, {
      "name": "kids",
      "size": 0.03
    }, {
      "name": "happenin",
      "size": 0.022
    }]
  }]
}
body>svg {
  position: absolute;
  top: -80px;
  left: -160px;
}

text {
  font-size: 11px;
  pointer-events: none;
}

text.parent {
  fill: #1f77b4;
}

circle {
  fill: #ccc;
  stroke: #999;
  pointer-events: all;
}

circle.parent {
  fill: #1f77b4;
  fill-opacity: .1;
  stroke: steelblue;
}

circle.parent:hover {
  stroke: #ff7f0e;
  stroke-width: .5px;
}

circle.child {
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-cloud/1.2.4/d3.layout.cloud.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="graph"></div>
...