Mapbox & D3:如何创建适当的颜色缩放?
Mapbox & D3: How to create proper color scaling?
我正在玩一个 Mapbox 地图(用 React 编写),它目前提供以下输出:
我的问题:GeoJSON 对象的着色不正确。这意味着这两种形状的值都是 ~-4。事实上,左图的值为10,应该是纯蓝色,而右图的值为5,应该是蓝绿色。
这是 Mapbox GeoJSON 图层的代码:
export const COLOR_SCALE = d3
.scaleSequential(d3.interpolateYlGnBu)
.domain([-4, 16])
.range([
[255, 255, 217],
[248, 252, 201],
[239, 249, 189],
[228, 244, 181],
[213, 238, 179],
[193, 231, 181],
[169, 221, 183],
[142, 211, 186],
[115, 201, 189],
[91, 191, 192],
[69, 180, 194],
[52, 167, 194],
[40, 151, 191],
[33, 134, 185],
[32, 115, 178],
[34, 96, 169],
[35, 78, 160],
[34, 62, 149],
[28, 49, 133],
[19, 38, 112],
]);
var layers = [
new GeoJsonLayer({
id: "geojson",
data: data,
filled: true,
getFillColor: (f) => COLOR_SCALE(f.properties.growth),
}),
];
这是色标代码:
useEffect(() => {
var colorScale1 = d3.scaleSequential(d3.interpolateYlGnBu).domain([-4, 16]);
var svg = continuous("#legend1", colorScale1);
}, []);
其中svg
用作React组件。最后,如果需要,我是这样定义的 continuous
:
function continuous(selector_id, colorscale) {
var legendheight = 200,
legendwidth = 80,
margin = { top: 10, right: 60, bottom: 10, left: 2 };
var canvas = d3
.select(selector_id)
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("position", "relative")
.append("canvas")
.attr("height", legendheight - margin.top - margin.bottom)
.attr("width", 1)
.style("height", legendheight - margin.top - margin.bottom + "px")
.style("width", legendwidth - margin.left - margin.right + "px")
.style("border", "1px solid #000")
.style("position", "absolute")
.style("top", margin.top + "px")
.style("left", margin.left + "px")
.node();
var ctx = canvas.getContext("2d");
var legendscale = d3
.scaleLinear()
.range([1, legendheight - margin.top - margin.bottom])
.domain(colorscale.domain());
var image = ctx.createImageData(1, legendheight);
d3.range(legendheight).forEach(function (i) {
var c = d3.rgb(colorscale(legendscale.invert(i)));
image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g;
image.data[4 * i + 2] = c.b;
image.data[4 * i + 3] = 255;
});
ctx.putImageData(image, 0, 0);
var legendaxis = d3.axisRight().scale(legendscale).tickSize(6).ticks(8);
var svg = d3
.select(selector_id)
.append("svg")
.attr("height", legendheight + "px")
.attr("width", legendwidth + "px")
.style("position", "absolute")
.style("left", "0px")
.style("top", "0px");
svg
.append("g")
.attr("class", "axis")
.attr(
"transform",
"translate(" +
(legendwidth - margin.left - margin.right + 3) +
"," +
margin.top +
")"
)
.call(legendaxis);
}
如何配置 COLOR_SCALE
以使形状正确着色?我怀疑错误源于 range
值。对于上下文,我通过使用 console.log(colorScale1(i))
.
进行迭代从 colorScale1
获得了 RGB 值
颜色“暗示两个形状的值都为 ~-4”- 鉴于您的比例范围,这是预期的。
A sequential scale has exactly two elements in its range array (generally specified with an interpolator rather than an array). Providing an array of more than two elements means all those beyond the first two are ignored - so you're interpolating between the 1st ( ) and 2nd ( ) elements in the provided range array - these elements represent values around -4, so everything will look like it is around ~ -4 when compared with a scale that has the intended range ([ , ]) - 就像图例中使用的那样。
将数组提供给 .range 实际上会在某些版本的 d3 中导致错误 - 仅使用插值器是更常见的方法
虽然我无法在这里重现您的确切问题 - 为两者回收相同的比例(或者最简单地完全删除范围值,否则两个色标是相同的)应该有效:您想要相同的表示图例或地图中的值相同。
下面我用矩形代替地理特征,但应该与你的情况类似:
var legendheight = 100;
var legendwidth = 40;
var colorscale = d3.scaleSequential(d3.interpolateYlGnBu)
.domain([-4, 16]);
var legendscale = d3
.scaleLinear()
.range([0, legendheight])
.domain(colorscale.domain());
var canvas = d3
.select("body")
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("position", "relative")
.append("canvas")
.attr("height", legendheight)
.attr("width", 1)
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("border", "1px solid #000")
.style("position", "absolute")
.style("top", 10 + "px")
.style("left", 0 + "px")
.node();
var ctx = canvas.getContext("2d");
var legendscale = d3
.scaleLinear()
.range([0, legendheight])
.domain(colorscale.domain());
var image = ctx.createImageData(1, legendheight);
d3.range(legendheight).forEach(function (i) {
var c = d3.rgb(colorscale(legendscale.invert(i)));
image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g;
image.data[4 * i + 2] = c.b;
image.data[4 * i + 3] = 255;
});
ctx.putImageData(image, 0, 0);
var legendaxis = d3.axisRight()
.scale(legendscale)
.tickSize(6)
.ticks(8);
d3.select("svg")
.append("g")
.attr("class", "axis")
.attr(
"transform",
"translate(" +
legendwidth +
",10)"
)
.call(legendaxis);
d3.select("svg")
.selectAll("rect")
.data(d3.range(30).map(function(d,i) {
return i / 29 * 20 - 4
}))
.enter()
.append("rect")
.attr("x", (d,i)=>i%10*20+100)
.attr("y", (d,i)=>Math.floor(i/10)*20+10)
.attr("width",16)
.attr("height",16)
.attr("fill",d=>colorscale(d));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width=500 height=300></svg>
我正在玩一个 Mapbox 地图(用 React 编写),它目前提供以下输出:
我的问题:GeoJSON 对象的着色不正确。这意味着这两种形状的值都是 ~-4。事实上,左图的值为10,应该是纯蓝色,而右图的值为5,应该是蓝绿色。
这是 Mapbox GeoJSON 图层的代码:
export const COLOR_SCALE = d3
.scaleSequential(d3.interpolateYlGnBu)
.domain([-4, 16])
.range([
[255, 255, 217],
[248, 252, 201],
[239, 249, 189],
[228, 244, 181],
[213, 238, 179],
[193, 231, 181],
[169, 221, 183],
[142, 211, 186],
[115, 201, 189],
[91, 191, 192],
[69, 180, 194],
[52, 167, 194],
[40, 151, 191],
[33, 134, 185],
[32, 115, 178],
[34, 96, 169],
[35, 78, 160],
[34, 62, 149],
[28, 49, 133],
[19, 38, 112],
]);
var layers = [
new GeoJsonLayer({
id: "geojson",
data: data,
filled: true,
getFillColor: (f) => COLOR_SCALE(f.properties.growth),
}),
];
这是色标代码:
useEffect(() => {
var colorScale1 = d3.scaleSequential(d3.interpolateYlGnBu).domain([-4, 16]);
var svg = continuous("#legend1", colorScale1);
}, []);
其中svg
用作React组件。最后,如果需要,我是这样定义的 continuous
:
function continuous(selector_id, colorscale) {
var legendheight = 200,
legendwidth = 80,
margin = { top: 10, right: 60, bottom: 10, left: 2 };
var canvas = d3
.select(selector_id)
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("position", "relative")
.append("canvas")
.attr("height", legendheight - margin.top - margin.bottom)
.attr("width", 1)
.style("height", legendheight - margin.top - margin.bottom + "px")
.style("width", legendwidth - margin.left - margin.right + "px")
.style("border", "1px solid #000")
.style("position", "absolute")
.style("top", margin.top + "px")
.style("left", margin.left + "px")
.node();
var ctx = canvas.getContext("2d");
var legendscale = d3
.scaleLinear()
.range([1, legendheight - margin.top - margin.bottom])
.domain(colorscale.domain());
var image = ctx.createImageData(1, legendheight);
d3.range(legendheight).forEach(function (i) {
var c = d3.rgb(colorscale(legendscale.invert(i)));
image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g;
image.data[4 * i + 2] = c.b;
image.data[4 * i + 3] = 255;
});
ctx.putImageData(image, 0, 0);
var legendaxis = d3.axisRight().scale(legendscale).tickSize(6).ticks(8);
var svg = d3
.select(selector_id)
.append("svg")
.attr("height", legendheight + "px")
.attr("width", legendwidth + "px")
.style("position", "absolute")
.style("left", "0px")
.style("top", "0px");
svg
.append("g")
.attr("class", "axis")
.attr(
"transform",
"translate(" +
(legendwidth - margin.left - margin.right + 3) +
"," +
margin.top +
")"
)
.call(legendaxis);
}
如何配置 COLOR_SCALE
以使形状正确着色?我怀疑错误源于 range
值。对于上下文,我通过使用 console.log(colorScale1(i))
.
colorScale1
获得了 RGB 值
颜色“暗示两个形状的值都为 ~-4”- 鉴于您的比例范围,这是预期的。
A sequential scale has exactly two elements in its range array (generally specified with an interpolator rather than an array). Providing an array of more than two elements means all those beyond the first two are ignored - so you're interpolating between the 1st (
将数组提供给 .range 实际上会在某些版本的 d3 中导致错误 - 仅使用插值器是更常见的方法
虽然我无法在这里重现您的确切问题 - 为两者回收相同的比例(或者最简单地完全删除范围值,否则两个色标是相同的)应该有效:您想要相同的表示图例或地图中的值相同。
下面我用矩形代替地理特征,但应该与你的情况类似:
var legendheight = 100;
var legendwidth = 40;
var colorscale = d3.scaleSequential(d3.interpolateYlGnBu)
.domain([-4, 16]);
var legendscale = d3
.scaleLinear()
.range([0, legendheight])
.domain(colorscale.domain());
var canvas = d3
.select("body")
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("position", "relative")
.append("canvas")
.attr("height", legendheight)
.attr("width", 1)
.style("height", legendheight + "px")
.style("width", legendwidth + "px")
.style("border", "1px solid #000")
.style("position", "absolute")
.style("top", 10 + "px")
.style("left", 0 + "px")
.node();
var ctx = canvas.getContext("2d");
var legendscale = d3
.scaleLinear()
.range([0, legendheight])
.domain(colorscale.domain());
var image = ctx.createImageData(1, legendheight);
d3.range(legendheight).forEach(function (i) {
var c = d3.rgb(colorscale(legendscale.invert(i)));
image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g;
image.data[4 * i + 2] = c.b;
image.data[4 * i + 3] = 255;
});
ctx.putImageData(image, 0, 0);
var legendaxis = d3.axisRight()
.scale(legendscale)
.tickSize(6)
.ticks(8);
d3.select("svg")
.append("g")
.attr("class", "axis")
.attr(
"transform",
"translate(" +
legendwidth +
",10)"
)
.call(legendaxis);
d3.select("svg")
.selectAll("rect")
.data(d3.range(30).map(function(d,i) {
return i / 29 * 20 - 4
}))
.enter()
.append("rect")
.attr("x", (d,i)=>i%10*20+100)
.attr("y", (d,i)=>Math.floor(i/10)*20+10)
.attr("width",16)
.attr("height",16)
.attr("fill",d=>colorscale(d));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width=500 height=300></svg>