前言:
页面一共分为两个结构
文字 + 渐变柱形图为一个部分,下面的标注为一个结构。
我们先看文字 + 渐变柱形图部分。
总体使用 flex 布局,左边文字部分占总体的 50%,右边的占剩余的空间部分。右侧渐变柱形部分的宽度是动态变化的。宽度是根据传入的 value,进行计算的。
<section className="graphs" style={style}> | |
<div className="chart-1"> | |
{listData.map((item, index) => { | |
return ( | |
<div className="chart-2" key={index}> | |
<div className="chart-3"> | |
<span>{item.name}</span> | |
</div> | |
<div className="chart-4"> | |
<div style={{ width: `${item.percent}%`, height: 24 }} /> | |
</div> | |
</div> | |
) | |
})} | |
</div> | |
</section> | |
.graphs { | |
width: 100%; | |
position: relative; | |
.chart-1 { | |
.chart-2 { | |
display: flex; | |
.chart-3 { | |
flex: 0 0 auto; | |
width: 50%; | |
} | |
.chart-4 { | |
flex: 1 1 auto; | |
} | |
} | |
} | |
} |
下方的标注部分,使用绝对定位,width = 50%,占父元素整体的一半,left = 50%,让其定位在右侧。这样就实现了,标注和渐变柱形部分的重叠。
这部分将 li 标签的 width = 1px,height = 100%,间隔通过 left 来动态实现。
<div className="bar-10"> | |
<ul className="chart-11"> | |
{scaleArray.map((item, itemIndex) => { | |
return ( | |
<li | |
className="chart-12" | |
style={{ left: `${(100 / scaleNum) * itemIndex}%` }} | |
key={itemIndex} | |
> | |
<span>{item}</span> | |
</li> | |
) | |
})} | |
</ul> | |
</div> | |
.bar-10 { | |
position: absolute; | |
top: 0px; | |
height: 100%; | |
width: 50%; | |
left: 50%; | |
box-sizing: border-box; | |
.chart-11 { | |
height: 100%; | |
position: relative; | |
width: 100%; | |
.chart-12 { | |
position: absolute; | |
top: -3px; | |
width: 1px; | |
height: 89%; | |
border-right: 1px solid #d7dbe0; | |
} | |
} | |
} |
关于数值的计算,这里笔者是找到这一组数据里面的最大值
let maxValue = 0; | |
data.forEach((dataItem) => { | |
if (dataItem.value > maxValue) maxValue = dataItem.value; | |
}); |
获取最大值最近的100整数
let maxScaleNum = Math.ceil(maxValue / 100) * 100
求取最小公倍数
let lcm = getLcm(maxScaleNum, scaleNum)
计算每一个数据的 value,占最小公倍数的百分比。
percent: (dataItem.value / lcm) * 100
标注的left,使用 for 循环生成。
const newArray = new Array(); | |
for (let i = 0; i <= lcm; i += lcm / scaleNum) { | |
newArray.push(i); | |
} |
整体代码:
import React, { useState, useEffect } from 'react'; | |
import ReactDom from 'react-dom'; | |
function getGcd(a, b) { | |
let max = Math.max(a, b); | |
let min = Math.min(a, b); | |
if (max % min === 0) { | |
return min; | |
} else { | |
return getGcd(max % min, min); | |
} | |
} | |
function getLcm(a, b) { | |
return a * b / getGcd(a, b); | |
} | |
const Test = ({ data, style, scaleNum = 5 }) => { | |
const [listData, setListData] = useState([]) | |
const [scaleArray, setScaleArray] = useState([]) | |
useEffect(() => { | |
if (scaleNum <= 0) { | |
return | |
} | |
let maxValue = 0 | |
data.forEach((dataItem) => { | |
if (dataItem.value > maxValue) maxValue = dataItem.value | |
}) | |
let maxScaleNum = Math.ceil(maxValue / 100) * 100 | |
let lcm = getLcm(maxScaleNum, scaleNum) | |
if (maxValue <= 0) { | |
const newArray = [0] | |
let number = 0 | |
for (let i = 0; i < scaleNum; i++) { | |
newArray.push((number += 20)) | |
} | |
setScaleArray(newArray) | |
setListData( | |
data.map((dataItem) => { | |
return { | |
name: dataItem.name, | |
percent: 0, | |
value: dataItem.value | |
} | |
}) | |
) | |
return | |
} | |
setListData( | |
data.map((dataItem) => { | |
return { | |
name: dataItem.name, | |
percent: (dataItem.value / lcm) * 100, | |
value: dataItem.value | |
} | |
}) | |
) | |
const newArray = new Array() | |
for (let i = 0; i <= lcm; i += (lcm / scaleNum)) { | |
newArray.push(i) | |
} | |
setScaleArray(newArray) | |
}, [data, scaleNum]) | |
return ( | |
<section className="graphs" style={style}> | |
<div className="chart-1"> | |
{listData.map((item, index) => { | |
return ( | |
<div className="chart-2" key={index}> | |
<div className="chart-3"> | |
<span>{item.name}</span> | |
</div> | |
<div className="chart-4"> | |
<div style={{ width: `${item.percent}%`, height: 24 }} /> | |
</div> | |
</div> | |
) | |
})} | |
</div> | |
<div className="bar-5"> | |
<ul className="chart-6"> | |
{scaleArray.map((item, itemIndex) => { | |
return ( | |
<li | |
className="chart-7" | |
style={{ left: `${(100 / scaleNum) * itemIndex}%` }} | |
key={itemIndex} | |
> | |
<span>{item}</span> | |
</li> | |
) | |
})} | |
</ul> | |
</div> | |
</section> | |
) | |
} | |
ReactDom.render(<Test style={{ width: 440 }} | |
scaleNum={6} | |
data={[ | |
{ | |
name: '西瓜', | |
value: 40 | |
}, | |
{ | |
name: '菠萝', | |
value: 56 | |
}, | |
{ | |
name: '香蕉', | |
value: 47 | |
} | |
]} />, document.getElementById('app')); | |
运行结果: