|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
rect.zoom-panel { |
|
cursor: move; |
|
fill: #fff; |
|
pointer-events: all; |
|
} |
|
|
|
.bar { |
|
fill: #43d1b6; |
|
} |
|
</style> |
|
<body> |
|
<script src="//d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
let data = [] |
|
let ordinals = [] |
|
|
|
for(let i = 0;i < 100; i++){ |
|
data.push({ |
|
value: Math.random()*10, |
|
city: 'Item' + i |
|
}) |
|
|
|
ordinals.push('Item' + i) |
|
} |
|
|
|
const margin = {top: 50, right: 100, bottom: 50, left: 100}, |
|
width = 1000 - margin.left - margin.right, |
|
height = 700 - margin.top - margin.bottom |
|
|
|
|
|
// d3.zoom - 创建缩放行为 |
|
// zoom.translateExtent - 设置平移范围 |
|
// zoom.extent - 设置viewport的尺寸 |
|
const svg = d3.select('body') |
|
.append('svg') |
|
.attr('width', 960) |
|
.attr('height', 700) |
|
.append('g') |
|
.attr('transform', `translate(${margin.left}, ${margin.top})`) |
|
.call( |
|
d3.zoom() |
|
.translateExtent([[0,0], [width, height]]) |
|
.extent([[0, 0], [width, height]]) |
|
.on('zoom', zoom) |
|
) |
|
|
|
// the scale |
|
let x = d3.scaleLinear().range([0, width]) |
|
let y = d3.scaleLinear().range([height, 0]) |
|
let xScale = x.domain([-1, ordinals.length]) |
|
let yScale = y.domain([0, d3.max(data, d => d.value)]) |
|
// for the width of rect |
|
let xBand = d3.scaleBand().domain(d3.range(-1, ordinals.length)).range([0, width]) |
|
|
|
// zoomable rect |
|
svg.append('rect') |
|
.attr('class', 'zoom-panel') |
|
.attr('width', width) |
|
.attr('height', height) |
|
|
|
// 绘制x轴 |
|
let xAxis = svg.append('g') |
|
.attr('class', 'xAxis') |
|
.attr('transform', `translate(0, ${height})`) |
|
.call( |
|
d3.axisBottom(xScale) |
|
.tickFormat((d, i) => ordinals[d]) |
|
) |
|
|
|
// 绘制y轴 |
|
let yAxis = svg.append('g') |
|
.attr('class', 'y axis') |
|
.call(d3.axisLeft(yScale)) |
|
|
|
let bars = svg.append('g') |
|
.attr('clip-path','url(#my-clip-path)') |
|
.selectAll('.bar') |
|
.data(data) |
|
.enter() |
|
.append('rect') |
|
.attr('class', 'bar') |
|
.attr('x', (d, i) => xScale(i) - xBand.bandwidth() * 0.9 / 2) |
|
.attr('y', (d, i) => yScale(d.value)) |
|
.attr('width', xBand.bandwidth() * 0.9) |
|
.attr('height', d => height - yScale(d.value)) |
|
// .attr('fill', (d,i) => color(i)) |
|
|
|
let defs = svg.append('defs') |
|
// use clipPath |
|
defs.append('clipPath') |
|
.attr('id', 'my-clip-path') |
|
.append('rect') |
|
.attr('width', width) |
|
.attr('height', height) |
|
|
|
// 如果没有文字则隐藏tick节点 |
|
let hideTicksWithoutLabel = function() { |
|
d3.selectAll('.xAxis .tick text').each(function(d){ |
|
if(this.innerHTML === '') { |
|
this.parentNode.style.display = 'none' |
|
} |
|
}) |
|
} |
|
|
|
// 比如初始柱子数目为100, 缩放的k值为1,缩放到柱子数目为5, 放大大概20倍,k值差不多是20 |
|
function zoom() { |
|
|
|
// 限制不能缩放的过小 |
|
if (d3.event.transform.k < 1) { |
|
d3.event.transform.k = 1 |
|
return |
|
} |
|
|
|
// 限制不能缩放的过大 |
|
if (d3.event.transform.k > 20) { |
|
d3.event.transform.k = 20 |
|
return |
|
} |
|
|
|
// 这里的意思大概是随着缩放,调整x坐标轴的显示,但是有点看不懂 |
|
xAxis.call( |
|
d3.axisBottom(d3.event.transform.rescaleX(xScale)).tickFormat((d, e, target) => { |
|
// has bug when the scale is too big |
|
if (Math.floor(d) === d3.format(".1f")(d)) return ordinals[Math.floor(d)] |
|
return ordinals[d] |
|
}) |
|
) |
|
|
|
hideTicksWithoutLabel() |
|
|
|
// 对bar进行缩放 |
|
bars.attr("transform", `translate(${d3.event.transform.x}, 0) scale(${d3.event.transform.k}, 1)`) |
|
} |
|
</script> |