1、先看效果。
哔哩哔哩在线解读演示:
中国近三年上市公司信息统计(主板、创业板、科创板)-web数据可视化(时间选择器)-000009_哔哩哔哩_bilibili
中国近三年上市公司信息统计(主板、创业板、科创板)-web数据可视化(时间选择器)-000009
截图:
2、数据及可视化含义。
哈喽,大家好,今天要通过地图+日期选择器的数据可视化跟大家分享一下中国近三年上市公司的信息。
屏幕中央是中国地图,在地图上有红色、黄色、绿色的圆圈,红色圆圈代表主板上市公司,黄色圆圈代表创业板上市公司,绿色圆圈代表科创板上市公司,1个圆圈代表1家上市公司,圆圈大小代表公司市值,放大地图,可以看到圆圈所在地图上的位置,它代表这些上市公司所在的城市。
屏幕左侧是数据信息窗,可以看出从9月3日到8月30日统计的这145周时间中,总共有949家公司上市,其中主板上市公司总共338家,创业板上市公司总共287家,科创板上市公司总共324家,平均每周有6.5家公司上市。
屏幕下方是日期选择器,柱形图展示了按ipo日期以周划分的上市公司的数量。X轴是日期,Y周是公司数量;一个柱子表示一周,它们高低不同,柱形图越高说明这一周上市公司的数量就越多。通过鼠标拖动可以选择周的数量,换句话说就是可以选择起始和终止的时间,现在选择的时间是——到——,其中它包含了——周,地图上显示的就是在这段时间内ipo的所有公司,鼠标要是选择主板、创业板、或者科创板,在地图上显示的就只有相对应板块的上市公司。拖动——周所占的这个方框,可以固定选择的周数,时间轴上选择的时间范围发生了变化,相对应的左边图例中的数据也会发生变化。
点击开始动画,在地图上会显示我们选择的这一板块每一周的情况,可以随时暂停,向前或者向后调节,也可以继续动画开始加速或者减速。点击重置,动画又会从头开始。点击重置过滤,界面恢复初始状态。
3、代码分析及讲解。
如下为时间选择柱形图和动画的代码。
// setup the bar chart (used to filter weeks)function barChart() {//TODO: remove this instance counter (in this viz, there is only one instance)if (!barChart.id) barChart.id = 0;var margin = {top: 10, right: 10, bottom: 20, left: 50},x,y = d3.scale.linear().range([59, 0]),id = barChart.id++,axis = d3.svg.axis().tickFormat(zh.timeFormat("%Y年%m月")).orient("bottom"),brush = d3.svg.brush(),brushDirty,dimension,group,round,svg,gBrush,enableBrush,theDiv;// main bar chart function; will be called repeatedly by renderAllfunction chart(div) {// determine dimensions of svgvar width = x.range()[1],height = y.range()[0];// update y scale domainy.domain([0, group.top(1)[0].value]); // set y domain to max value in this group//TODO: inefficient code; div is an array of onediv.each(function() {var div = theDiv = d3.select(this);var g = div.select("g");// if g is empty, reubild the svgif (g.empty()) {// create svg and groupg = div.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 + ")").on("click", function() {// exit the animatorif (anim) anim.exit();//TODO: need to handle the case of mouse drag as well...})// reset the clip path to full widthg.append("clipPath").attr("id", "clip-" + id).append("rect").attr("width", width).attr("height", height);// generate two paths, one background, one foregroundg.selectAll(".bar").data(["background", "foreground"]).enter().append("path").attr("class", function(d) { return d + " bar"; })// assign all the data in the group to the path.datum(group.all());// assign the clip path to the foreground barsg.selectAll(".foreground.bar").attr("clip-path", "url(#clip-" + id + ")");// Initialize the brush component with pretty resize handles.if (enableBrush) {gBrush = g.append("g").attr("class", "brush").call(brush);gBrush.selectAll("rect").attr("height", height);gBrush.selectAll(".resize").append("path").attr("d", resizePath);}// add the x-axis last (so it's drawn on top of brush)g.append("g").attr("class", "axis").attr("transform", "translate(0," + height + ")").call(axis);}// Only redraw the brush if set externally.// at init, the date chart has an externally set brushif (brushDirty) {brushDirty = false;g.selectAll(".brush").call(brush);div.select(".title a").style("display", brush.empty() ? "none" : null);if (brush.empty()) {g.selectAll("#clip-" + id + " rect").attr("x", 0).attr("width", width);} else {var extent = brush.extent();g.selectAll("#clip-" + id + " rect").attr("x", x(extent[0])).attr("width", x(extent[1]) - x(extent[0]));}}// set the d attribute on the pathg.selectAll(".bar").attr("d", barPath);});// generate the bar chart path itemfunction barPath(groups, j, a) {var path = [],i = -1,n = groups.length,d;while (++i < n) {d = groups[i];path.push("M", x(d.key), ",", height, "V", y(d.value), "h4V", height);}return path.join("");}// generate pretty brush left and right "handles"function resizePath(d) {var e = +(d == "e"),x = e ? 1 : -1,y = height / 3;return "M" + (.5 * x) + "," + y+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)+ "V" + (2 * y - 6)+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)+ "Z"+ "M" + (2.5 * x) + "," + (y + 8)+ "V" + (2 * y - 8)+ "M" + (4.5 * x) + "," + (y + 8)+ "V" + (2 * y - 8);}}// brush handlersbrush.on("brushstart.chart", function() {// get the containing divvar div = d3.select(this.parentNode.parentNode.parentNode);// remove the display property from the reset anchor elemdiv.select(".title a").style("display", null);});brush.on("brush.chart", function() {var g = d3.select(this.parentNode),extent = brush.extent();// handle rounding of extent (only integers)if (round) g.select(".brush").call(brush.extent(extent = extent.map(round))) // set a rounded brush extent.selectAll(".resize").style("display", null); // remove the resize handles (why?)// update clip rectangleg.select("#clip-" + id + " rect").attr("x", x(extent[0])).attr("width", x(extent[1]) - x(extent[0]));// update the filterdimension.filterRange(extent);});brush.on("brushend.chart", function() {if (brush.empty()) {emptyBrush.call(this);}});// function to sync UI and crossfilter to an empty brushfunction emptyBrush() {//console.log("emptyBrush", this);if (!brush.empty()) {console.error("brush not empty")return;}// get reference to containing divvar div = d3.select(this.parentNode.parentNode.parentNode);// hide the reset anchor elementdiv.select(".title a").style("display", "none");// remove the clip rectanglediv.select("#clip-" + id + " rect").attr("x", null) // remove the x attribute which will render the clipRect invalid.attr("width", "100%");// reset the filterdimension.filterAll();}// chart configuration functionschart.margin = function(_) {if (!arguments.length) return margin;margin = _;return chart;};chart.x = function(_) {if (!arguments.length) return x;x = _;axis.scale(x);brush.x(x);return chart;};chart.y = function(_) {if (!arguments.length) return y;y = _;return chart;};chart.dimension = function(_) {//console.log("chart.dimension..." + _)if (!arguments.length) return dimension;dimension = _;return chart;};chart.filter = function(_) {if (_) {brush.extent(_);dimension.filterRange(_);} else {brush.clear();dimension.filterAll();}brushDirty = true;return chart;};chart.group = function(_) {if (!arguments.length) return group;group = _;return chart;};chart.round = function(_) {if (!arguments.length) return round;round = _;return chart;};chart.removeContents = function() {theDiv.selectAll("svg").remove();//theDiv.selectAll("a").remove();//console.log("theDiv", theDiv)return chart;}chart.brushDirty = function(_) {if (!arguments.length) return brushDirty;brushDirty = _;return chart;}chart.enableBrush = function(_) {if (!arguments.length) return enableBrush;enableBrush = _;return chart;}chart.clearBrush = function() {//console.log("---", d3.select(".brush"))d3.select(".brush").call(brush.clear())emptyBrush.call(d3.select(".brush").node())return chart;}chart.brushExtent = function(_) {if (!arguments.length) return brush.extent();//console.log("setting brush extent...", _, "current", brush.extent())brush.extent(_);d3.select(".brush").call(brush);brush.event(d3.select(".brush"))return chart;}// copy "on" event handlers from "brush" to "chart"return d3.rebind(chart, brush, "on");}// animator object/functionfunction animator() {var speed = 1000,currPos = 0,started = false,animating = false,data,ready = false,intHandle,toHandle,inTimeout = false,chart,container;function main(_) {data = _;if (data.length > 0) ready = true;// data good?if (!ready) {console.error("bad data")return;}// container good?if (!container) {console.error("No animation control container provided")return;}// event handlersfunction startStop() {this.blur();var elem = d3.select(this)var value = elem.attr("value")//console.log("startStop...", this, value)if (value == "start") {// start the animation...started = true;main.resume();// change this button textelem.text("退出动画").attr("value", "stop")// show buttonsd3.selectAll(".anim2").style("display", "inline-block")d3.selectAll(".anim4").style("display", "inline-block")} else {// stop the animationstarted = false;main.stop();// change this button textelem.text("开始动画").attr("value", "start")// hide buttonsd3.selectAll(".anim").style("display", "none")// ensure the halt/resume button says haltd3.select(".anim2").text("暂停").attr("value", "halt")}}function haltResume() {this.blur();var elem = d3.select(this)var value = elem.attr("value")//console.log("haltResume...", this, value)if (value == "halt") {// halt the animationmain.stop();// change this button textelem.text("继续").attr("value", "resume")// hide buttonsd3.selectAll(".anim4").style("display", "none")// show buttonsd3.selectAll(".anim3").style("display", "inline-block")} else {// resume the animationmain.resume();// change this button textelem.text("暂停").attr("value", "halt")// hide buttonsd3.selectAll(".anim3").style("display", "none")// show buttonsd3.selectAll(".anim4").style("display", "inline-block")}}function fasterSlower() {this.blur();var elem = d3.select(this)var value = elem.attr("value")//console.log("fasterSlower...", this, value)if (value == "faster") main.faster()else main.slower();}function forwardBackward() {this.blur();var elem = d3.select(this)var value = elem.attr("value")//console.log("forwardBackward...", this, value, this.value)if (value == "forward") main.forward()else main.backward();}function reset() {this.blur();main.reset();}// animation controlsvar controls = [{ text: "开始动画", handler: startStop, id: "startStop", display: "inline-block", value: "start", class: "anim1", width: "110px" },{ text: "暂停", handler: haltResume, display: "none", value: "halt", class: "anim anim2", width: "60px" },{ text: "向后", handler: forwardBackward, display: "none", value: "backward", class: "anim anim3" },{ text: "向前", handler: forwardBackward, display: "none", value: "forward", class: "anim anim3" },{ text: "加速", handler: fasterSlower, display: "none", value: "faster", class: "anim anim4" },{ text: "减速", handler: fasterSlower, display: "none", value: "slower", class: "anim anim4" },{ text: "重置", handler: reset, display: "none", class: "anim anim3 anim4" }]container.selectAll("button").data(controls).enter().append("button").attr("id", function(d) { return d.id ? "anim_" + d.id : null }).style("display", "inline-block").style("cursor", "default").style("width", function(d) { return d.width ? d.width : "70px" }).style("text-align", "center").attr("class", function(d) { return d.class }).style("display", function(d) { /*return "inline-block";*/ return d.display }).attr("value", function(d) { return d.value ? d.value : null })//.attr("class", "reset").text(function(d) { return d.text }).on("click", function(d) { /*console.log("adfads", d.handler);*/ d.handler.call(this) })// set indexcurrPos = data.length - 1;intHandle = setInterval(function() {if (animating && !inTimeout) go(0);}, 100)function go(useSpeed) {inTimeout = true;toHandle = setTimeout(function() {if (animating) {currPos++;if (currPos == data.length) currPos = 0;oneCycle();}if (animating) go(speed);else inTimeout = false;}, useSpeed)}}function oneCycle() {//console.log("oneCycle")var dateExtent = [];dateExtent[0] = new Date(data[currPos].key);dateExtent[1] = new Date(data[currPos].key);dateExtent[1].setDate(dateExtent[1].getDate() + 7)chart.brushExtent(dateExtent)}// action functionsmain.reset = function() {speed = 1000;currPos = 0;oneCycle();//animating = false;}main.resume = function() {animating = true;}main.stop = function() {animating = false;}main.exit = function() {animating = false;// change this button textd3.select("#anim_startStop").text("开始动画").attr("value", "start")// hide buttonsd3.selectAll(".anim").style("display", "none")// ensure the halt/resume button says haltd3.select(".anim2").text("暂停").attr("value", "halt")}main.slower = function() {speed = Math.min(4000, speed * 2);}main.faster = function() {speed = Math.max(250, speed / 2);}main.forward = function() {if (ready && !animating) {currPos++;if (currPos == data.length) currPos = 0;oneCycle();}else console.log("not ready");}main.backward = function() {if (ready && !animating) {currPos--;if (currPos < 0) currPos = data.length - 1;oneCycle();}else console.log("not ready");}// configuration functionsmain.speed = function(_) {if (!arguments.length) return speed;speed = _;return main;}main.chart = function(_) {if (!arguments.length) return chart;chart = _;return main;}main.container = function(_) {if (!arguments.length) return container;container = _;return main;}return main;} // end animator