我创建了此示例热图: Plunker
最初我使用某个线性刻度colorScale1
为热图着色。
当用户点击图例时,会更新色标并使用阈值比例(colorScale2
)。
此开关效果很好。
现在我不知道如何更改colorScale2
的图例。
colorScale2
的刻度和渐变是错误的。我为linearGradient
寻找了scaleThreshold
等价物,但我找不到任何东西。
这是代码:
var itemSize = 20;
var cellBorderSize = 1;
var cellSize = itemSize - 1 + cellBorderSize;
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var width = 80 - margin.right - margin.left;
var height = 80 - margin.top - margin.bottom;
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var domain1 = [0, 80, 90, 95, 100];
var range1 = ['#EC93AB', '#CEB1DE', '#95D3F0', '#77EDD9', '#A9FCAA'];
var colorScale1 = d3.scaleLinear()
.domain(domain1)
.range(range1);
var domain2 = [0, 95, 100];
var range2 = ['white', 'lightgrey', 'grey'];
var colorScale2 = d3.scaleThreshold()
.domain(domain2)
.range(range2);
svg.append('defs')
.append('pattern')
.attr('id', 'pattern-stripes')
.attr('patternUnits', 'userSpaceOnUse')
.attr('patternTransform', 'rotate(45)')
.attr('width', 3)
.attr('height', 3)
.append('rect')
.attr('width', 1)
.attr('height', 3)
.attr('transform', 'translate(0, 0)')
.attr('fill', 'black');
///////////////////////////////////////////////////////////
// Load data files.
///////////////////////////////////////////////////////////
var files = ['./data.csv'];
var promises = [];
promises.push(d3.csv(files[0]));
Promise.all(promises)
.then(makeHeatmap)
.catch(function(err) {
console.log('Error loading files');
throw err;
});
///////////////////////////////////////////////////////////
// Data heatmap
///////////////////////////////////////////////////////////
function makeHeatmap(myData) {
var data = myData[0];
// get each element of data file and creates an object
var data = data.map(function(item) {
var newItem = {};
newItem.name = item.NAME;
newItem.year = item.YEAR;
newItem.val = item.VAL;
return newItem;
});
var names = data.map(function(d) {
return d.name;
});
regionsName = d3.set(names).values();
numRegions = regionsName.length;
var years = data.map(function(d) {
return d.year;
});
yearsName = d3.set(years).values();
numYears = yearsName.length;
///////////////////////////////////////////////////////////
// Draw heatmap
///////////////////////////////////////////////////////////
var cells = svg.selectAll('.cell')
.data(data)
.enter()
.append('g')
.append('rect')
.attr('data-value', function(d) {
return d.val;
})
.attr('data-r', function(d) {
var idr = regionsName.indexOf(d.name);
return idr;
})
.attr('data-c', function(d, i) {
if(regionsName.includes(d.name) & d.year == '1990') var idc = 0;
else if(regionsName.includes(d.name) && d.year == '1991') var idc = 1;
else if(regionsName.includes(d.name) && d.year == '1992') var idc = 2;
return idc;
})
.attr('class', function() {
var idr = d3.select(this).attr('data-r'); // row
var idc = d3.select(this).attr('data-c'); // column
return 'cell cr' + idr + ' cc' + idc;
})
.attr('width', cellSize)
.attr('height', cellSize)
.attr('x', function(d) {
var c = d3.select(this).attr('data-c');
return c * cellSize;
})
.attr('y', function() {
var r = d3.select(this).attr('data-r');
return r * cellSize;
})
.attr('fill', function(d) {
var col;
if(d.name == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
} // end makeHeatmap
///////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////
// create tick marks
var xLegend = d3.scaleLinear()
.domain([0, 100])
.range([10, 409]); // larghezza dei tick
var axisLegend = d3.axisBottom(xLegend)
.tickSize(19) // height of ticks
.tickFormat(function(v, i) { // i is index of domain colorScale, v is the corrisponding value (v = domain[i])
if(v == 0) {
return v + '%';
}
else {
return v;
}
})
.tickValues(colorScale1.domain());
var svgLegend = d3.select('#legend').append('svg').attr('width', 600);
// append title
svgLegend.append('text')
.attr('class', 'legendTitle')
.attr('x', 10)
.attr('y', 20)
.style('text-anchor', 'start')
.text('Legend title');
// draw the rectangle and fill with gradient
svgLegend.append('rect')
.attr('class', 'legendRect')
.attr('x', 10) // position
.attr('y', 30)
.attr('width', 400) // larghezza fascia colorata
.attr('height', 15) // altezza fascia colorata
.style('fill', 'url(#linear-gradient1)')
.on('click', function() {
if(currentFill === '1') {
updateColor2();
currentFill = '2';
}
else {
updateColor1();
currentFill = '1';
}
});
svgLegend
.attr('class', 'legendLinAxis')
.append('g')
.attr('class', 'legendLinG')
.attr('transform', 'translate(0, 30)') // 47 è la posizione verticale dei tick (se l'aumenti, scendono) (47 per farli partire sotto, 30 per farli partire da sopra)
.call(axisLegend);
var defs = svgLegend.append('defs');
// horizontal gradient and append multiple color stops by using D3's data/enter step
var linearGradient1 = defs.append('linearGradient')
.attr('id', 'linear-gradient1')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(colorScale1.domain())
.enter().append('stop')
.attr('offset', function(d) {
return d + '%';
})
.attr('stop-color', function(d) {
return colorScale1(d);
});
// horizontal gradient and append multiple color stops by using D3's data/enter step
var linearGradient2 = defs.append('linearGradient')
.attr('id', 'linear-gradient2')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(colorScale2.domain())
.enter().append('stop')
.attr('offset', function(d) {
return d + '%';
})
.attr('stop-color', function(d) {
return colorScale2(d);
});
// update the colors to a different color scale (colorScale1)
function updateColor1() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient1)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
}
// update the colors to a different color scale (colorScale2)
function updateColor2() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient2)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale2(d.val);
}
return col;
});
}
// start set-up
updateColor1();
var currentFill = '1';
答案 0 :(得分:1)
以下是代码的略微修改版本,点击图例时会更新图例的刻度和色阶:
var itemSize = 20;
var cellBorderSize = 1;
var cellSize = itemSize - 1 + cellBorderSize;
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var width = 80 - margin.right - margin.left;
var height = 80 - margin.top - margin.bottom;
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var domain1 = [0, 80, 90, 95, 100];
var range1 = ['#EC93AB', '#CEB1DE', '#95D3F0', '#77EDD9', '#A9FCAA'];
var colorScale1 = d3.scaleLinear()
.domain(domain1)
.range(range1);
var domain2 = [0, 95, 100];
var range2 = ['white', 'lightgrey', 'grey'];
var colorScale2 = d3.scaleThreshold()
.domain(domain2)
.range(range2);
svg.append('defs')
.append('pattern')
.attr('id', 'pattern-stripes')
.attr('patternUnits', 'userSpaceOnUse')
.attr('patternTransform', 'rotate(45)')
.attr('width', 3)
.attr('height', 3)
.append('rect')
.attr('width', 1)
.attr('height', 3)
.attr('transform', 'translate(0, 0)')
.attr('fill', 'black');
var data = [
{ "NAME": "ronnie", "YEAR": 1990, "VAL": 90 },
{ "NAME": "ronnie", "YEAR": 1991, "VAL": 95 },
{ "NAME": "ronnie", "YEAR": 1992, "VAL": 98 },
{ "NAME": "bob", "YEAR": 1990, "VAL": 92 },
{ "NAME": "bob", "YEAR": 1991, "VAL": 90 },
{ "NAME": "bob", "YEAR": 1992, "VAL": 99 },
{ "NAME": "carl", "YEAR": 1990, "VAL": 98 },
{ "NAME": "carl", "YEAR": 1991, "VAL": 99 },
{ "NAME": "carl", "YEAR": 1992, "VAL": 995 }
];
makeHeatmap(data);
///////////////////////////////////////////////////////////
// Data heatmap
///////////////////////////////////////////////////////////
function makeHeatmap(data) {
//var data = myData[0];
// get each element of data file and creates an object
var data = data.map(function(item) {
var newItem = {};
newItem.name = item.NAME;
newItem.year = item.YEAR;
newItem.val = item.VAL;
return newItem;
});
var names = data.map(function(d) {
return d.name;
});
regionsName = d3.set(names).values();
numRegions = regionsName.length;
var years = data.map(function(d) {
return d.year;
});
yearsName = d3.set(years).values();
numYears = yearsName.length;
///////////////////////////////////////////////////////////
// Draw heatmap
///////////////////////////////////////////////////////////
var cells = svg.selectAll('.cell')
.data(data)
.enter()
.append('g')
.append('rect')
.attr('data-value', function(d) {
return d.val;
})
.attr('data-r', function(d) {
var idr = regionsName.indexOf(d.name);
return idr;
})
.attr('data-c', function(d, i) {
if(regionsName.includes(d.name) & d.year == '1990') var idc = 0;
else if(regionsName.includes(d.name) && d.year == '1991') var idc = 1;
else if(regionsName.includes(d.name) && d.year == '1992') var idc = 2;
return idc;
})
.attr('class', function() {
var idr = d3.select(this).attr('data-r'); // row
var idc = d3.select(this).attr('data-c'); // column
return 'cell cr' + idr + ' cc' + idc;
})
.attr('width', cellSize)
.attr('height', cellSize)
.attr('x', function(d) {
var c = d3.select(this).attr('data-c');
return c * cellSize;
})
.attr('y', function() {
var r = d3.select(this).attr('data-r');
return r * cellSize;
})
.attr('fill', function(d) {
var col;
if(d.name == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
} // end makeHeatmap
///////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////
// create tick marks
var xLegend = d3.scaleLinear()
.domain([0, 100])
.range([10, 409]); // larghezza dei tick
var axisLegend = d3.axisBottom(xLegend)
.tickSize(19) // height of ticks
.tickFormat(function(v, i) { // i is index of domain colorScale, v is the corrisponding value (v = domain[i])
if(v == 0) {
return v + '%';
}
else {
return v;
}
});
var svgLegend = d3.select('#legend').append('svg').attr('width', 600);
// append title
svgLegend.append('text')
.attr('class', 'legendTitle')
.attr('x', 10)
.attr('y', 20)
.style('text-anchor', 'start')
.text('Legend title');
// draw the rectangle and fill with gradient
svgLegend.append('rect')
.attr('class', 'legendRect')
.attr('x', 10) // position
.attr('y', 30)
.attr('width', 400) // larghezza fascia colorata
.attr('height', 15) // altezza fascia colorata
.style('fill', 'url(#linear-gradient1)')
.on('click', function() {
if(currentFill === '1') {
updateColor2();
currentFill = '2';
}
else {
updateColor1();
currentFill = '1';
}
});
var legend = svgLegend
.attr('class', 'legendLinAxis')
.append('g')
.attr('class', 'legendLinG')
.attr('transform', 'translate(0, 30)'); // 47 è la posizione verticale dei tick (se l'aumenti, scendono) (47 per farli partire sotto, 30 per farli partire da sopra)
var defs = svgLegend.append('defs');
// horizontal gradient and append multiple color stops by using D3's data/enter step
var linearGradient1 = defs.append('linearGradient')
.attr('id', 'linear-gradient1')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(colorScale1.domain())
.enter().append('stop')
.attr('offset', function(d) {
return d + '%';
})
.attr('stop-color', function(d) {
return colorScale1(d);
});
// horizontal gradient and append multiple color stops by using D3's data/enter step
function getGradient2data() {
// Duplicates elements of domain2:
var duplicatedDomain = domain2.reduce(function (res, current, index, array) { return res.concat([current, current]); }, []).slice(1, -1);
// Duplicates elements of range2:
var duplicatedRange = range2.slice(1).reduce(function (res, current, index, array) { return res.concat([current, current]); }, []);
// Zips both domain and range:
return duplicatedDomain.map( function(e, i) { return { "offset": e + "%", "color": duplicatedRange[i] }; [e, duplicatedRange[i]]; });
}
var linearGradient2 = defs.append('linearGradient')
.attr('id', 'linear-gradient2')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data(getGradient2data())
//.data([
// { offset: "0%", color: "lightgrey" },
// { offset: "95%", color: "lightgrey" },
// { offset: "95%", color: "grey" },
// { offset: "100%", color: "grey" }
//])
.enter().append('stop')
.attr('offset', function(d) {
return d.offset;
})
.attr('stop-color', function(d) {
return d.color;
});
// update the colors to a different color scale (colorScale1)
function updateColor1() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient1)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale1(d.val);
}
return col;
});
axisLegend.tickValues(colorScale1.domain());
legend.call(axisLegend);
}
// update the colors to a different color scale (colorScale2)
function updateColor2() {
// fill the legend rectangle
svgLegend.select('.legendRect')
.style('fill', 'url(#linear-gradient2)');
// transition the cell colors
svg.selectAll('.cell')
.transition().duration(1000)
.style('fill', function(d, i) {
var col;
if(d.valuePol == '') {
col = 'url(#pattern-stripes)';
}
else {
col = colorScale2(d.val);
}
return col;
});
axisLegend.tickValues(colorScale2.domain());
legend.call(axisLegend);
}
// start set-up
updateColor1();
var currentFill = '1';
#heatmap {
float: left;
background-color: whitesmoke;
}
.cell {
stroke: #E6E6E6;
stroke-width: 1px;
}
/**
* Legend linear.
*/
.legendTitle {
font-size: 15px;
fill: black;
font-weight: 12;
font-family: Consolas, courier;
}
#legendLin {
background-color: yellow;
}
.legendLinAxis path, .legendLinAxis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.legendLinAxis text {
font-family: Consolas, courier;
font-size: 8pt;
fill: black;
}
.legendLinG .tick line {
stroke: black;
stroke-width: 1px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="./style.css" media="screen"/>
</head>
<body>
<div id='heatmap'></div>
<div id='legend'></div>
<script src="./script.js"></script>
</body>
</html>
嘀嗒更新:
在更新函数(updateColor1
,updateColor2
)中,除了更新颜色标度渐变之外,我们还可以包含图例标记的更新(类似于它首次初始化的方式) :
axisLegend.tickValues(colorScale1.domain());
legend.call(axisLegend);
渐变更新:
"abrupt gradients"的创建与“线性渐变”略有不同。以下是linear-gradient2
阈值渐变的略微修改版本:
var linearGradient2 = defs.append('linearGradient')
.attr('id', 'linear-gradient2')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '0%')
.selectAll('stop')
.data([
{ offset: "0%", color: "lightgrey" },
{ offset: "95%", color: "lightgrey" },
{ offset: "95%", color: "grey" },
{ offset: "100%", color: "grey" }
])
.enter().append('stop')
.attr('offset', function(d) { return d.offset; })
.attr('stop-color', function(d) { return d.color; });
或者,如果要更改阈值梯度,而不是对其进行硬编码,我们也可以从定义的域和范围中获取它:
function getGradient2data() {
// Duplicates elements of domain2:
var duplicatedDomain = domain2.reduce(function (res, current, index, array) { return res.concat([current, current]); }, []).slice(1, -1);
// Duplicates elements of range2:
var duplicatedRange = range2.slice(1).reduce(function (res, current, index, array) { return res.concat([current, current]); }, []);
// Zips both domain and range:
return duplicatedDomain.map( function(e, i) { return { "offset": e + "%", "color": duplicatedRange[i] }; [e, duplicatedRange[i]]; });
}
产生:
[
{ offset: "0%", color: "lightgrey" },
{ offset: "95%", color: "lightgrey" },
{ offset: "95%", color: "grey" },
{ offset: "100%", color: "grey" }
]