D3.js研发分区柱状图
d3.js研发分区柱状图
特殊要求:分三个区域 0-0.2、0.2-0.3、other三个区域,其中0.3必须标注
因为坐标轴的要求,其实在这个图中是画了两个x轴,然后隐藏掉了常规x轴,显示了特殊x轴
下面是项目里封装好的方法 可以直接调用,上代码:
import * as d3 from "d3";const CVDistributionBar = (options = {}) => {let originContainer = document.querySelector("#chart-container"),originHeight = originContainer.offsetHeight,originWidth = originContainer.offsetWidth;let height = originHeight * (options.params.height / 100);let width = originWidth * (options.params.width / 100);const container = d3.select(options.container);let svg = container.select("svg");// 获取标签样式function getSvgTextStyle({text = "",fontSize = 14,fontFamily = "Arial",fontWeight = "normal"} = {}) {const svg = d3.select("body").append("svg").attr("class", "get-svg-text-style");const textStyle = svg.append("text").text(text).attr("font-size", fontSize).attr("font-family", fontFamily).attr("font-weight", fontWeight).node().getBBox();svg.remove();return {width: textStyle.width,height: textStyle.height};}// 获取线性坐标轴宽高function getSvgBandAxisStyle({fontSize = 20,orient = "bottom",fontFamily = "Arial",fontWeight = "normal",rotate = 0,domain = ["A", "B", "C"],range = [0, 200]} = {}) {let axis;let svg = d3.select("body").append("svg").attr("width", 200).attr("height", 100).attr("transform", "translate(300, 200)").attr("class", "get-svg-axis-style");let scale = d3.scaleBand().domain(domain).range(range);if (orient === "bottom" || orient === "top") {axis = d3.axisBottom(scale);} else {axis = d3.axisLeft(scale);}let axisStyle = svg.append("g").call(axis).call((g) => {g.selectAll("text").attr("fill", "#555").attr("font-size", fontSize).attr("font-family", fontFamily).attr("font-weight", fontWeight).attr("tmpY",g.select("text").attr("tmpY") || g.select("text").attr("dy")).attr("dy",rotate > 70 && rotate <= 90? "0.35em": rotate >= -90 && rotate < -70? "0.4em": g.select("text").attr("tmpY")).attr("text-anchor",orient === "left"? "end": rotate? rotate > 0? "start": "end": "middle").attr("transform",`translate(0, 0) ${rotate ? `rotate(${rotate} 0 ${g.select("text").attr("y")})` : ""}`);}).node().getBBox();svg.remove();return {width: axisStyle.width,height: axisStyle.height};}const rawData = options.data.barData;const cvLabel = options.data.cvLabel;// 配置参数let {bar_witdh = 25,color1 = "pink",color2 = "#E0D1F1",color3 = "gray",cutoff_color = "red",cutoff_size = 1,x_title = "111",x_title_color = "#000",x_title_font = "Arial",x_title_size = 12,x_text_rotate = 45,x_text_color = "#000000",x_text_size = 14,x_text_font = "Arial",y_title = "222",y_title_color = "#000",y_title_font = "Arial",y_title_size = 14,y_text_color = "#000000",y_text_size = 14,y_text_font = "Arial",main_title = "333",main_title_color = "#000",main_title_font = "Arial",main_title_size = 14} = options.params;x_text_rotate = -x_text_rotate;// 数据预处理const data = rawData.map((d) => {const parts = d.cv.split(/[~]/).map(Number);return {...d,start: parts[0],end: parts[1] || parts[0] + 0.05};});// let xDomain = [0, 1.2];let xDomain = options.data.xDomain;let yDomain = [0, d3.max(data, (d) => d.density)];const mainTitleH = main_title? getSvgTextStyle({text: main_title,fontSize: main_title_size,fontFamily: main_title_font}).height + 20: 0;const xTitleH = x_title? getSvgTextStyle({text: x_title,fontSize: x_title_size,fontFamily: x_title_font}).height + 20: 0;const xAxisH =getSvgBandAxisStyle({fontSize: x_text_size,fontFamily: x_text_font,rotate: x_text_rotate,domain: xDomain}).height + 20;const yTitleH = y_title? getSvgTextStyle({text: y_title,fontSize: y_title_size,fontFamily: y_title_font}).height + 20: 0;const yAxisW =getSvgBandAxisStyle({fontSize: y_text_size,fontFamily: y_text_font,domain: yDomain,orient: "left"}).width + 20;// 图表参数const margin = {top: 30 + mainTitleH,right: 50,bottom: 30 + xAxisH + xTitleH,left: 30 + yAxisW + yTitleH};// 创建SVG画布if (svg.empty()) {svg = container.append("svg");} else {svg.selectAll("*").remove();}svg.attr("width", width).attr("height", height);// 比例尺const xScale = d3.scaleLinear().domain(xDomain).range([margin.left, width - margin.right]);const yScale = d3.scaleLinear().domain(yDomain).nice().range([height - margin.bottom, margin.top]);// 绘制柱状图svg.selectAll("rect").data(data).join("rect").attr("x", (d) => xScale(d.start)).attr("width",bar_witdh// (d) => {// const w = xScale(d.end) - xScale(d.start);// console.log(w);// return w > 0 ? w - 1 : 0;// }).attr("y", (d) => yScale(d.density)).attr("height", (d) => yScale(0) - yScale(d.density)).attr("fill", (d) => {if (d.end <= 0.2) return color1; // 0-0.2区间if (d.start >= 0.2 && d.end <= 0.3) return color2; // 0.2-0.3区间return color3; // 0.3+区间});// 主标题svg.append("text").attr("class", "chart-title").attr("x", width / 2).attr("y", margin.top - 20).text(main_title).attr("text-anchor", "middle").attr("font-family", main_title_font).attr("font-size", main_title_size).attr("fill", main_title_color);// X轴标题svg.append("text").attr("class", "axis-title").attr("x", width / 2).attr("y", height - margin.bottom + xAxisH + xTitleH / 2).text(x_title).attr("text-anchor", "middle").attr("font-family", x_title_font).attr("font-size", x_title_size).attr("fill", x_title_color);// Y轴标题svg.append("text").attr("class", "axis-title").attr("transform", `rotate(-90)`).attr("x", -height / 2).attr("y", margin.left - yAxisW - yTitleH / 2).text(y_title).attr("text-anchor", "middle").attr("font-family", y_title_font).attr("font-size", y_title_size).attr("fill", y_title_color);// 创建X轴const requiredXTicks = [xDomain[0], 0.3, xDomain[1]]; // 必须包含的刻度const generatedXTicks = xScale.ticks(4); // 自动生成的刻度const mergedXTicks = Array.from(new Set([...generatedXTicks, ...requiredXTicks])).sort((a, b) => a - b).filter((t) => t >= xDomain[0] && t <= xDomain[1]);const xAxis = d3.axisBottom(xScale).tickValues(mergedXTicks).tickFormat((d) => d3.format(".1f")(d));svg.append("g").attr("class", "x-axis").attr("transform", `translate(0,${height - margin.bottom})`) // 保持底部定位.call(xAxis).call((g) => {g.selectAll(".tick text").attr("fill", x_text_color).attr("font-size", `${x_text_size}px`).attr("font-family", x_text_font).each(function () {const text = d3.select(this);text.attr("tmpY", text.attr("dy") || "0.71em"); // 默认值处理}).attr("dy", (d, i, nodes) => {const text = d3.select(nodes[i]);const rotate = x_text_rotate;if (rotate > 70 && rotate <= 90) return "0.35em";if (rotate >= -90 && rotate < -70) return "0.4em";return text.attr("tmpY");}).attr("text-anchor", (d) =>x_text_rotate ? (x_text_rotate > 0 ? "start" : "end") : "middle").attr("transform", (d, i, nodes) =>x_text_rotate? `rotate(${x_text_rotate} 0 ${d3.select(nodes[i]).attr("y")})`: "");});// 创建Y轴const yAxis = d3.axisLeft(yScale).tickSize(5).tickPadding(8);svg.append("g").attr("class", "y-axis").attr("transform", `translate(${margin.left},0)`).call(yAxis).selectAll("path") // 坐标轴线样式.attr("stroke", "#333").attr("stroke-width", 1.5).attr("fill", "none").attr("shape-rendering", "crispEdges");// 设置刻度文本样式svg.selectAll(".y-axis .tick text").attr("font-family", y_text_font).style("font-size", y_text_size).style("fill", y_text_color);// 添加卡值线const thresholdX = xScale(0.3);svg.append("line").attr("stroke", cutoff_color).attr("stroke-width", cutoff_size)// .attr("stroke-dasharray", "4") //虚线.attr("x1", thresholdX).attr("x2", thresholdX).attr("y1", margin.top).attr("y2", height - margin.bottom);// 添加阈值标注// const total = d3.sum(data, (d) => d.density);// const belowSum = d3.sum(// data.filter((d) => d.end <= 0.3),// (d) => d.density// );svg.append("text").attr("fill", "#ff0000").style("font-size", "12px").style("font-weight", "bold").attr("x", thresholdX + 10).attr("y", margin.top + 20).text(cvLabel);// 设置坐标轴刻度线样式svg.selectAll(".tick line").attr("stroke", "#333").attr("stroke-width", 1).attr("shape-rendering", "crispEdges");
};export default CVDistributionBar;
调用:
CVDistributionBar({data: plots,params: chartParam,container: "#bar-container"});
献上研发初期没封装的html版的吧!可直接运行
<!DOCTYPE html>
<html><head><title>CV Distribution</title><script src="https://d3js.org/d3.v7.min.js"></script></head><body><div id="chart"></div></body><script>// 获取标签样式function getSvgTextStyle({text = "",fontSize = 14,fontFamily = "Arial",fontWeight = "normal"} = {}) {const svg = d3.select("body").append("svg").attr("class", "get-svg-text-style");const textStyle = svg.append("text").text(text).attr("font-size", fontSize).attr("font-family", fontFamily).attr("font-weight", fontWeight).node().getBBox();svg.remove();return {width: textStyle.width,height: textStyle.height};}// 获取线性坐标轴宽高function getSvgBandAxisStyle({fontSize = 20,orient = "bottom",fontFamily = "Arial",fontWeight = "normal",rotate = 0,domain = ["A", "B", "C"],range = [0, 200]} = {}) {let axis;let svg = d3.select("body").append("svg").attr("width", 200).attr("height", 100).attr("transform", "translate(300, 200)").attr("class", "get-svg-axis-style");let scale = d3.scaleBand().domain(domain).range(range);if (orient === "bottom" || orient === "top") {axis = d3.axisBottom(scale);} else {axis = d3.axisLeft(scale);}let axisStyle = svg.append("g").call(axis).call((g) => {g.selectAll("text").attr("fill", "#555").attr("font-size", fontSize).attr("font-family", fontFamily).attr("font-weight", fontWeight).attr("tmpY",g.select("text").attr("tmpY") || g.select("text").attr("dy")).attr("dy",rotate > 70 && rotate <= 90? "0.35em": rotate >= -90 && rotate < -70? "0.4em": g.select("text").attr("tmpY")).attr("text-anchor",orient === "left"? "end": rotate? rotate > 0? "start": "end": "middle").attr("transform",`translate(0, 0) ${rotate? `rotate(${rotate} 0 ${g.select("text").attr("y")})`: ""}`);}).node().getBBox();svg.remove();return {width: axisStyle.width,height: axisStyle.height};}// 原始数据const rawData = [{ cv: "0~0.05", density: 3082 },{ cv: "0.05~0.1", density: 2109 },{ cv: "0.1~0.15", density: 1100 },{ cv: "0.15~0.2", density: 621 },{ cv: "0.2~0.25", density: 430 },{ cv: "0.25~0.3", density: 256 },{ cv: "0.3~0.35", density: 177 },{ cv: "0.35~0.4", density: 127 },{ cv: "0.4~0.45", density: 87 },{ cv: "0.45~0.5", density: 58 },{ cv: "0.5~0.55", density: 60 },{ cv: "0.55~0.6", density: 33 },{ cv: "0.6~0.65", density: 21 },{ cv: "0.65~0.7", density: 22 },{ cv: "0.7~0.75", density: 14 },{ cv: "0.75~0.8", density: 7 },{ cv: "0.8~0.85", density: 12 },{ cv: "0.85~0.9", density: 9 },{ cv: "0.9~0.95", density: 3 },{ cv: "0.95~1", density: 2 },{ cv: "1~1.05", density: 2 },{ cv: "1.05~1.1", density: 1 }];const cvLabel = "cv<0.3 73.16%";// 配置参数let // {bar_witdh = 35,color1 = "pink",color2 = "#E0D1F1",color3 = "gray",cutoff_color = "red",cutoff_size = 1,x_title = "111",x_title_color = "#000",x_title_font = "Arial",x_title_size = 14,x_text_rotate = -45,x_text_color = "#000000",x_text_size = 14,x_text_font = "Arial",y_title = "222",y_title_color = "#000",y_title_font = "Arial",y_title_size = 14,y_text_color = "#000000",y_text_size = 14,y_text_font = "Arial",main_title = "333",main_title_color = "#000",main_title_font = "Arial",main_title_size = 14;// } = options.params;// 数据预处理const data = rawData.map((d) => {const parts = d.cv.split(/[~]/).map(Number);return {...d,start: parts[0],end: parts[1] || parts[0] + 0.05};});let xDomain = [0, 1.2];let yDomain = [0, d3.max(data, (d) => d.density)];const mainTitleH = main_title? getSvgTextStyle({text: main_title,fontSize: main_title_size,fontFamily: main_title_font}).height + 20: 0;const xTitleH = x_title? getSvgTextStyle({text: x_title,fontSize: x_title_size,fontFamily: x_title_font}).height + 20: 0;const xAxisH =getSvgBandAxisStyle({fontSize: x_text_size,fontFamily: x_text_font,rotate: x_text_rotate,domain: xDomain}).height + 20;const yTitleH = y_title? getSvgTextStyle({text: y_title,fontSize: y_title_size,fontFamily: y_title_font}).height + 20: 0;const yAxisW =getSvgBandAxisStyle({fontSize: y_text_size,fontFamily: y_text_font,domain: yDomain,orient: "left"}).width + 20;// 图表参数const margin = {top: mainTitleH,right: 50,bottom: xAxisH + xTitleH,left: yAxisW + yTitleH};const width = 1000;const height = 500;const innerHeight = height - margin.top - margin.bottom;const innerWidth = width - margin.left - margin.right;// 创建SVG画布const svg = d3.select("#chart").append("svg").attr("width", width).attr("height", height);// 比例尺const xScale = d3.scaleLinear().domain(xDomain).range([margin.left, width - margin.right]);const yScale = d3.scaleLinear().domain(yDomain).nice().range([height - margin.bottom, margin.top]);// 绘制柱状图svg.selectAll("rect").data(data).join("rect").attr("x", (d) => xScale(d.start)).attr("width",bar_witdh// (d) => {// const w = xScale(d.end) - xScale(d.start);// console.log(w);// return w > 0 ? w - 1 : 0;// }).attr("y", (d) => yScale(d.density)).attr("height", (d) => yScale(0) - yScale(d.density)).attr("fill", (d) => {if (d.end <= 0.2) return color1; // 0-0.2区间if (d.start >= 0.2 && d.end <= 0.3) return color2; // 0.2-0.3区间return color3; // 0.3+区间});// 主标题svg.append("text").attr("class", "chart-title").attr("x", width / 2).attr("y", margin.top - 20).text(main_title).attr("text-anchor", "middle").attr("font-family", main_title_font).attr("font-size", main_title_size).attr("fill", main_title_color);// X轴标题svg.append("text").attr("class", "axis-title").attr("x", width / 2).attr("y", height - margin.bottom + xAxisH + xTitleH / 2).text(x_title).attr("text-anchor", "middle").attr("font-family", x_title_font).attr("font-size", x_title_size).attr("fill", x_title_color);// Y轴标题svg.append("text").attr("class", "axis-title").attr("transform", `rotate(-90)`).attr("x", -height / 2).attr("y", margin.left - yAxisW - yTitleH / 2).text(y_title).attr("text-anchor", "middle").attr("font-family", y_title_font).attr("font-size", y_title_size).attr("fill", y_title_color);// 创建X轴const requiredXTicks = [xDomain[0], 0.3, xDomain[1]]; // 必须包含的刻度const generatedXTicks = xScale.ticks(4); // 自动生成的刻度const mergedXTicks = Array.from(new Set([...generatedXTicks, ...requiredXTicks])).sort((a, b) => a - b).filter((t) => t >= xDomain[0] && t <= xDomain[1]);const xAxis = d3.axisBottom(xScale).tickValues(mergedXTicks).tickFormat((d) => d3.format(".1f")(d));svg.append("g").attr("class", "x-axis").attr("transform", `translate(0,${height - margin.bottom})`) // 保持底部定位.call(xAxis).call((g) => {g.selectAll(".tick text").attr("fill", x_text_color).attr("font-size", x_text_size).attr("font-family", x_text_font).each(function () {const text = d3.select(this);text.attr("tmpY", text.attr("dy") || "0.71em"); // 默认值处理}).attr("dy", (d, i, nodes) => {const text = d3.select(nodes[i]);const rotate = x_text_rotate;if (rotate > 70 && rotate <= 90) return "0.35em";if (rotate >= -90 && rotate < -70) return "0.4em";return text.attr("tmpY");}).attr("text-anchor", (d) =>x_text_rotate ? (x_text_rotate > 0 ? "start" : "end") : "middle").attr("transform", (d, i, nodes) =>x_text_rotate? `rotate(${x_text_rotate} 0 ${d3.select(nodes[i]).attr("y")})`: "");});// 创建Y轴const yAxis = d3.axisLeft(yScale).tickSize(5).tickPadding(8);svg.append("g").attr("transform", `translate(${margin.left},0)`).call(yAxis).selectAll("path") // 坐标轴线样式.attr("stroke", "#333").attr("stroke-width", 1.5).attr("fill", "none").attr("shape-rendering", "crispEdges");// 设置刻度文本样式svg.selectAll(".tick text").attr("font-family", y_text_font).style("font-size", y_text_size).style("fill", y_text_color);// 添加卡值线const thresholdX = xScale(0.3);svg.append("line").attr("stroke", cutoff_color).attr("stroke-width", cutoff_size)// .attr("stroke-dasharray", "4") //虚线.attr("x1", thresholdX).attr("x2", thresholdX).attr("y1", margin.top).attr("y2", height - margin.bottom);// 添加阈值标注const total = d3.sum(data, (d) => d.density);const belowSum = d3.sum(data.filter((d) => d.end <= 0.3),(d) => d.density);svg.append("text").attr("fill", "#ff0000").style("font-size", "12px").style("font-weight", "bold").attr("x", thresholdX + 10).attr("y", margin.top + 20).text(cvLabel);// 设置坐标轴刻度线样式svg.selectAll(".tick line").attr("stroke", "#333").attr("stroke-width", 1).attr("shape-rendering", "crispEdges");</script>
</html>
效果图:
什么时候寒暑假也能带上打工人阿!!!要不上四休三也行阿!!!要不多发几倍工资吧!!!好想去玩 不想早起!!!