【发布时间】:2020-11-20 13:43:10
【问题描述】:
我有以下名为 chart 的 jsx 组件,我想将 IIFE (d3.legend.js) 移动到它自己的文件中。
import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import * as d3 from "d3";
/* d3.legend.js */
(function () {
d3.legend = function (g) {
g.each(function () {
const g = d3.select(this),
svg = d3.select(g.property("nearestViewportElement")),
legendPadding = g.attr("data-style-padding") || 5;
let items = {},
lb = g.selectAll(".legend-box").data([true]),
li = g.selectAll(".legend-items").data([true]);
lb = lb.enter().append("rect").classed("legend-box", true).merge(lb);
li = li.enter().append("g").classed("legend-items", true).merge(li);
svg.selectAll("[data-legend]").each(function () {
const self = d3.select(this);
items[self.attr("data-legend")] = {
pos: self.attr("data-legend-pos") || this.getBBox().y,
color:
self.attr("data-legend-color") ||
self.style("stroke") ||
self.style("fill")
};
});
items = Object.entries(items).sort(
([keyA, valA], [keyB, valB]) => valA.pos - valB.pos
);
li.selectAll("g")
.data(items, ([key]) => key)
.join(
(enter) => {
const g = enter
.append("g")
.attr("data-key", ([key, value]) => key)
.classed("legend-item", true);
g.append("text")
.attr("y", (d, i) => i * 1.25 + "em")
.attr("x", "1em")
.text(([key]) => key);
g.append("circle")
.attr("cy", (d, i) => i * 1.25 - 0.25 + "em")
.attr("cx", 0)
.attr("r", "0.4em")
.style("fill", ([key, value]) => value.color);
return g;
},
(update) => update,
(exit) => exit.remove()
);
// Reposition and resize the box
var lbbox = li.node().getBBox();
lb.attr("x", lbbox.x - legendPadding)
.attr("y", lbbox.y - legendPadding)
.attr("height", lbbox.height + 2 * legendPadding)
.attr("width", lbbox.width + 2 * legendPadding);
});
return g;
};
})();
const palette = [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999"
];
const Chart = (props) => {
const { config: { xField, yField, groupingKey }, data } = props;
const ref = useRef();
const margin = { top: 10, right: 60, bottom: 20, left: 60 },
width = 720 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
useEffect(() => {
const svgElement = d3.select(ref.current);
// Empty the children
svgElement.selectAll("*").remove();
// Inner-SVG
const svgInner = svgElement
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// Group the data: I want to draw one line per group
const groupedData = [
...d3.group(data, (d) => d[groupingKey])
].map(([key, values]) => ({ key, values }));
const halfway = Math.floor(groupedData.length / 2);
const dataLeft = groupedData.slice(0, halfway);
const dataRight = groupedData.slice(halfway);
// Add X axis --> it is a date format
const xScale = d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[xField]))
.range([0, width]);
svgInner
.append("g")
.attr("class", "d3-axis d3-axis-bottom")
.attr("transform", `translate(0, ${height})`)
.call(
d3
.axisBottom(xScale)
.ticks(2)
.tickFormat((year) => year.toString())
);
// Add Y axis
const yScaleLeft = d3
.scaleLinear()
.domain([
0,
d3.max(
dataLeft.flatMap((d) => d.values),
(d) => d[yField]
)
])
.range([height, 0]);
const yScaleRight = d3
.scaleLinear()
.domain([
0,
d3.max(
dataRight.flatMap((d) => d.values),
(d) => d[yField]
)
])
.range([height, 0]);
svgInner
.append("g")
.attr("class", "d3-axis d3-axis-left")
.call(d3.axisLeft(yScaleLeft));
svgInner
.append("g")
.attr("class", "d3-axis d3-axis-right")
.attr("transform", `translate(${width}, 0)`)
.call(d3.axisRight(yScaleRight));
// Color palette
const res = groupedData.map((d) => d.key).sort(); // list of group names
const color = d3.scaleOrdinal().domain(res).range(palette);
svgInner
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// Draw the line
svgInner
.selectAll(".line")
.data(dataLeft)
.enter()
.append("path")
.attr("fill", "none")
.attr("data-legend", (d) => d.key)
.attr("stroke", (d) => color(d.key))
.attr("stroke-width", 1.5)
.attr("d", (d) =>
d3
.line()
.x((d) => xScale(d[xField]))
.y((d) => yScaleLeft(+d[yField]))(d.values)
);
svgInner
.selectAll(".line")
.data(dataRight)
.enter()
.append("path")
.attr("fill", "none")
.attr("data-legend", (d) => d.key)
.attr("stroke", (d) => color(d.key))
.attr("stroke-width", 1.5)
.attr("d", (d) =>
d3
.line()
.x((d) => xScale(d[xField]))
.y((d) => yScaleRight(+d[yField]))(d.values)
);
let legend = svgInner
.append("g")
.classed("legend", true)
.attr("transform", "translate(30,20)")
.style("font-size", "12px")
.call(d3.legend);
}, [
data,
groupingKey,
xField,
yField,
width,
height,
margin.top,
margin.left,
margin.right
]);
return (
<div className="d3-chart">
<svg
ref={ref}
width={width + margin.left + margin.right}
height={height + margin.top + margin.bottom}
/>
</div>
);
};
Chart.defaultChartConfig = {
groupingKey: null,
xField: null,
yField: null
};
Chart.defaultProps = {
config: Chart.defaultChartConfig,
data: []
};
Chart.propTypes = {
config: PropTypes.object,
data: PropTypes.arrayOf(PropTypes.object)
};
export default Chart;
我将如何导出这个?
export default (() => { d3.legend = (g) => { /* ... */ } })()
所以我可以导入它并调用d3.legend?
注意:这里是snippet此时的代码。
【问题讨论】:
-
该 IIFE 最初的目的是什么?无论如何都没有发生范围污染,拥有 IIFE 似乎完全没用。
-
@str 是一个d3插件,我更新了,想集成。
-
假设你只想要副作用,你需要做
import 'myModule'。如果您想导出执行此更改的能力,则应导出执行此逻辑的函数。因此,将其从 IIFE 更改以避免立即执行。
标签: javascript reactjs d3.js