Axis Tips #5
基準線をデータの内容に合わせて移動する。
青枠はドラッグしてサイズを変更することができます。
| license: mit |
| <!DOCTYPE html> | |
| <html lang="jp"> | |
| <head> | |
| <style> | |
| html, body { | |
| margin: 0px; | |
| padding: 0px; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #chart { | |
| width: 80%; | |
| height: 80%; | |
| border: 8px dashed gray; | |
| border-left: none; | |
| border-top:none; | |
| cursor:all-scroll; | |
| } | |
| #chart svg{ | |
| width: 100%; | |
| height: 100%; | |
| cursor: default; | |
| } | |
| .grid .tick line { | |
| stroke-dasharray:1 4; | |
| } | |
| .axis .domain { | |
| stroke:red; | |
| stroke-width:3px; | |
| } | |
| .bar { | |
| stroke:gray; | |
| stroke-width:1; | |
| fill:skyblue; | |
| } | |
| </style> | |
| <script src="//unpkg.com/babel-standalone@6.26.0/babel.min.js"></script> | |
| <script src="//unpkg.com/d3@4.12.2/build/d3.min.js"></script> | |
| </head> | |
| <body> | |
| <div id="chart"> | |
| <svg></svg> | |
| </div> | |
| <script type="text/babel"> | |
| const Toggle = (...arg) =>{ | |
| var key = arg; | |
| var l = key.length; | |
| var i = 0; | |
| return function(){ | |
| if(l <= i) i=0; | |
| return key[i++]; | |
| } | |
| } | |
| const keyGen = Toggle("value1", "value2"); | |
| const data = [ | |
| {title:"アメリカ", value1:80, value2:-21}, | |
| {title:"日本", value1:40, value2:60}, | |
| {title:"中国", value1:60, value2:-10}, | |
| ] | |
| const svg = d3.select("#chart").select("svg"); | |
| const grid = svg.append("g").classed("grid", true); | |
| const plot = svg.append("g").classed("plot", true); | |
| const axis = svg.append("g").classed("axis", true); | |
| const yScale = d3.scaleBand().domain(data.map(d => d.title)); | |
| const xScale = d3.scaleLinear(); | |
| // value1, value2 を交互に出力するトグル。 | |
| let valueKey = keyGen(); | |
| //3秒ごとに参照するキーを変えてチャートをアップデートする。 | |
| setInterval(function(){ valueKey = keyGen();render() }, 3000) | |
| render(); | |
| function render(){ | |
| const m = {top:30, left:110, right:30, bottom:30}; | |
| const w = svg.node().clientWidth || svg.node().parentNode.clientWidth; | |
| const h = svg.node().clientHeight || svg.node().parentNode.clientHeight; | |
| const pw = w - (m.left + m.right); | |
| const ph = h - (m.top + m.bottom); | |
| const valueExtent = d3.extent(data, d => d[valueKey]).map( (d,i) =>{ | |
| if(i==0 && d > 0) return 0; | |
| return d * 1.5; | |
| }) ; | |
| xScale.domain(valueExtent).nice(); | |
| yScale.range([0, ph]).paddingInner(0.2); | |
| xScale.range([0, pw]); | |
| //axis layer | |
| axis.attr("transform", `translate(${m.left}, ${m.top})`); | |
| //y axis | |
| const yAxisUpdate = axis.selectAll(".yAxis").data([null]); | |
| const yAxisEnter = yAxisUpdate.enter().append("g").classed("yAxis", true); | |
| const yAxis = yAxisUpdate.merge(yAxisEnter).call( d3.axisLeft().scale(yScale).tickSizeOuter(0) ); | |
| yAxis.selectAll(".tick line").remove(); | |
| yAxis.select(".domain") | |
| .attr("transform", `translate(${xScale(0)}, 0)`); //ベースラインを0の位置へ移動する | |
| //x axis | |
| const xAxisUpdate = axis.selectAll(".xAxis").data([null]); | |
| const xAxisEnter = xAxisUpdate.enter().append("g").classed("xAxis", true); | |
| const renderAxis = d3.axisBottom().scale(xScale); | |
| const xAxis = xAxisUpdate.merge(xAxisEnter).call(renderAxis) | |
| .attr("transform", `translate(0, ${ph})`); | |
| xAxis.select(".domain").remove(); | |
| xAxis.selectAll(".tick line").remove(); | |
| //grid layer | |
| grid.attr("transform", `translate(${m.left}, ${m.top})`); | |
| //x grid | |
| const xGridUpdate = grid.selectAll(".xGrid").data([null]); | |
| const xGridEnter = xGridUpdate.enter().append("g").classed("xGrid", true); | |
| const xGrid = xGridUpdate.merge(xGridEnter) | |
| .call( d3.axisBottom().scale(xScale).tickSizeInner(-ph).tickFormat(() => null ) ) | |
| .attr("transform", `translate(0, ${ph})`); | |
| xGrid.select(".domain").remove(); | |
| //plot layer | |
| plot.attr("transform", `translate(${m.left}, ${m.top})`); | |
| //bar | |
| const barsUpdate = plot.selectAll(".bar").data(data); | |
| const barsEnter = barsUpdate.enter().append("rect").classed("bar", true); | |
| const bars = barsUpdate.merge(barsEnter) | |
| .attr("height", yScale.bandwidth()) | |
| .attr("width", d => Math.abs(xScale(d[valueKey]) - xScale(0)) ) | |
| .attr("y", d => yScale(d.title) ) | |
| .attr("x", d => xScale( Math.min(0, d[valueKey])) ) | |
| ; | |
| } | |
| //divエレメントをドラッグでリサイズできるようにする。 | |
| const dispatch = d3.dispatch("resize"); | |
| dispatch.on("resize", render); | |
| setResizeControler(); | |
| function setResizeControler(){ | |
| const drag = d3.drag() | |
| .on("drag", resized) | |
| d3.select("#chart") | |
| .call(drag); | |
| function resized(e){ | |
| const s = d3.event.sourceEvent; | |
| const w = (s.pageX < 300) ? 300 : s.pageX; | |
| const h = (s.pageY < 200) ? 200 : s.pageY; | |
| d3.select(this) | |
| .style("width", `${w}px`) | |
| .style("height", `${h}px`) | |
| .attr("data-test", "test") | |
| dispatch.call("resize"); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |