Create a gist now

Instantly share code, notes, and snippets.

@shimizu /.block
Last active Aug 25, 2016

What would you like to do?
D3 - use Proxy
license: mit
/**
*
* @module createVBarChart
* @desc セレクター上に棒グラフを描画します。
*/
function createVBarChart(){
"use strict"
var _chartWidth,_chartHeight
var _margin = {top:0, left:0, bottom:0, right:0};
var _x = function(){ return d },
_y = function(){ return d }
var _xScale = d3.scaleBand(),
_yScale = d3.scaleLinear()
var _xScaleDomain, _yScaleDomain,
_xScaleRange, _yScaleRange
var _xScalePaddingInner = 0.1,
_xScalePaddingOuter = 0.5
var _xAxisLabel,_yAxisLabel
var _xAxisGridVisible = false,
_yAxisGridVisible = false
var _xAxisLabelOption = {x:0, y:0, "text-anchor":"middle", "dominant-baseline":"auto"},
_yAxisLabelOption = {x:0, y:0, "text-anchor":"start", "dominant-baseline":"auto"}
var _yTickValues, _xTickValues
var _transitionObject = d3.transition().duration(0)
var _responsive = true
var _dispatch = d3.dispatch("mouseover","mousemove", "mouseout", "click");
function exports(_selection) {
_selection.each(function(_data){
var isHash = function(value) {
return value.toString() === '[object Object]';
}
var isArray = Array.isArray || function(value) {
return value.toString() === '[object Array]';
}
var parentNode = _selection.node()
var selectedSVG = _selection.selectAll("svg")
.data(["dummy"])
var newSVG = selectedSVG.enter().append("svg")
var svg = selectedSVG.merge(newSVG)
svg.attr("class", "vbarChart")
var axisLayer = svg.append("g").classed("axisLayer", true)
var chartLayer = svg.append("g").classed("chartLayer", true)
var parentWidth, parentHeight
main(_data)
if(_responsive) setReSizeEvent()
function main(data) {
setSize()
if(isHash(data)){
var tmp = []
Object.keys(data).forEach(function(key){
tmp.push(data[key])
})
setScale(Array.prototype.concat.apply([], tmp))
} else if (isArray(data)){
setScale(data)
}
if(_yAxisGridVisible) renderYAxisGrid()
if(_xAxisGridVisible) renderXAxisGrid()
renderYAxis()
renderXAxis()
renderYAxisLabel()
renderXAxisLabel()
renderBarChart(data)
}
function setReSizeEvent() {
var resizeTimer;
var interval = Math.floor(1000 / 60 * 10);
window.addEventListener('resize', function (event) {
if (resizeTimer !== false) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(function () {
main(_data)
}, interval);
});
}
function setSize(args) {
parentWidth = parentNode.clientWidth
parentHeight = parentNode.clientHeight
_chartWidth = parentWidth - (_margin.left + _margin.right)
_chartHeight = parentHeight - (_margin.top + _margin.bottom)
svg
.attr("width", parentWidth)
.attr("height", parentHeight)
axisLayer
.attr("width", parentWidth)
.attr("height", parentHeight)
chartLayer
.attr("width", _chartWidth)
.attr("height", _chartHeight)
.attr("transform", "translate("+[_margin.left, _margin.top]+")")
}
function setScale(data){
var xMap = data.map(function(d){ return _x(d) }).sort(function(a, b){ return a -b })
var yMax = d3.max(data, function(d){ return +_y(d) })
var yMin = d3.min(data, function(d){ return +_y(d) })
if (yMin < 0){
var yExtent = [yMin, yMax]
}else{
var yExtent = [0, yMax]
}
_xScaleDomain = xMap
_yScaleDomain = yExtent
_xScaleRange = [0, _chartWidth]
_yScaleRange = [_chartHeight, 0]
_xScale.domain(_xScaleDomain).paddingInner(_xScalePaddingInner).paddingOuter(_xScalePaddingOuter)
_yScale.domain(_yScaleDomain)
_xScale.range(_xScaleRange)
_yScale.range(_yScaleRange)
}
function renderYAxis() {
var yAxisCall = d3.axisLeft(_yScale)
.tickSizeOuter(0)
if (_yTickValues) yAxisCall.tickValues(_yTickValues)
var yAxis = axisLayer.selectAll(".axis.y")
.data(["dummy"])
var newYAxis = yAxis.enter().append("g")
newYAxis.merge(yAxis)
.transition(_transitionObject)
.attr("transform", "translate("+[_margin.left, _margin.top]+")")
.attr("class", "axis y")
.call(yAxisCall);
}
function renderYAxisGrid() {
var yAxisCall = d3.axisLeft(_yScale)
.tickSizeOuter(0)
.tickSizeInner(-_chartWidth)
.tickFormat(function(d){ return null })
if (_yTickValues) yAxisCall.tickValues(_yTickValues)
var yAxis = axisLayer.selectAll(".grid.y")
.data(["dummy"])
var newYAxis = yAxis.enter().append("g")
.attr("class", "grid y")
newYAxis.merge(yAxis)
.transition(_transitionObject)
.attr("transform", "translate("+[_margin.left, _margin.top]+")")
.call(yAxisCall);
}
function renderXAxis() {
var xAxisCall = d3.axisBottom(_xScale)
.tickSizeOuter(0)
if (_xTickValues) xAxisCall.tickValues(_xTickValues)
var xAxis = axisLayer.selectAll(".axis.x")
.data(["dummy"])
var newXAxis = xAxis.enter().append("g")
newXAxis.merge(xAxis)
.transition(_transitionObject)
.attr("transform", "translate("+[_margin.left, _chartHeight+_margin.top]+")")
.attr("class", "axis x")
.call(xAxisCall)
.each(function(){
d3.select(this).select(".domain")
.attr("transform", "translate("+[0,-_chartHeight + _yScale(0)]+")")
})
}
function renderXAxisGrid() {
var xAxisCall = d3.axisBottom(_xScale)
.tickSizeOuter(0)
.tickSizeInner(-_chartHeight)
.tickFormat(function(d){ return null })
if (_xTickValues) xAxisCall.tickValues(_xTickValues)
var xAxis = axisLayer.selectAll(".grid.x")
.data(["dummy"])
var newXAxis = xAxis.enter().append("g")
newXAxis.merge(xAxis)
.transition(_transitionObject)
.attr("transform", "translate("+[_margin.left, _chartHeight+_margin.top]+")")
.attr("class", "grid x")
.call(xAxisCall);
}
function renderYAxisLabel() {
var yAxisLabel = axisLayer.selectAll(".label.y")
.data(["dummy"])
var newYAxisLabel = yAxisLabel.enter().append("text").attr("class", "label y")
yAxisLabel.merge(newYAxisLabel)
.text(function(d){ return _yAxisLabel })
.attr("x", _yAxisLabelOption.x)
.attr("y", _yAxisLabelOption.y)
.attr("text-anchor", _yAxisLabelOption["text-anchor"])
.attr("dominant-baseline", _yAxisLabelOption["dominant-baseline"])
.attr("transform", "translate("+[_margin.left, _margin.top]+")")
}
function renderXAxisLabel() {
var xAxisLabel = axisLayer.selectAll(".label.x")
.data(["dummy"])
var newXAxisLabel = xAxisLabel.enter().append("text").attr("class", "label x")
xAxisLabel.merge(newXAxisLabel)
.text(function(d){ return _xAxisLabel })
.attr("x", _xAxisLabelOption.x)
.attr("y", _xAxisLabelOption.y)
.attr("text-anchor", _xAxisLabelOption["text-anchor"])
.attr("dominant-baseline", _xAxisLabelOption["dominant-baseline"])
.attr("transform", "translate("+[_chartWidth+_margin.left, _chartHeight+_margin.top]+")")
}
function renderBarChart(data) {
var bar = chartLayer.selectAll(".bar").data(data)
bar.exit().remove()
var newBar = bar.enter().append("rect")
.attr("class", function(d){ return "bar " + _x(d) })
.attr("height", 0)
.attr("transform", function(d){ return "translate("+[_xScale(_x(d)), _chartHeight]+")"})
bar.merge(newBar)
.attr("width", _xScale.bandwidth())
bar.merge(newBar).transition(_transitionObject)
.attr("height", function(d){
var height = Math.abs( _yScale(_y(d)) - _yScale(0) )
return height
})
.attr("transform", function(d){
var y = _yScale(Math.max(0, _y(d)))
return "translate("+[_xScale(_x(d)), y]+")"
})
}
_selection.update = function(data){
var _data = data
main(_data)
}
_selection._module = exports
})
}
exports.margin = function(_arg) {
if (!arguments.length) return _margin;
Object.keys(_arg).forEach(function(key){
_margin[key] = _arg[key]
})
return this;
}
exports.x = function(_arg) {
if (!arguments.length) return _x;
_x = _arg;
return this;
}
exports.y = function(_arg) {
if (!arguments.length) return _y;
_y = _arg;
return this;
}
exports.responsive = function(_arg) {
if (!arguments.length) return _responsive;
_responsive = _arg;
return this;
}
return exports
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>D3 - use Proxy</title>
<style>
html, body {
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
}
#chart {
width: 900px;
height: 450px;
}
.vbarChart .bar {
fill:blue;
}
/* axis */
.vbarChart .axis {
}
.vbarChart .axis .domain {
stroke: #333333;
}
.vbarChart .tick line {
stroke: #333333;
stroke-width: 1px;
}
.vbarChart .tick text {
fill: #333333;
font-size: 14px;
letter-spacing: .05em;
}
/* grid */
.vbarChart .grid line {
stroke: #cccccc;
stroke-dasharray: 3,3;
}
/* label */
.vbarChart .label {
font-size: 12px;
font-weight: normal;
letter-spacing: .05em;
}
#form {
margin: 10px;
}
</style>
</head>
<body>
<div id="form">
tokyo:<input class="input" type="number" name="tokyo">
gunma:<input class="input" type="number" name="gunma">
saitama:<input class="input" type="number" name="saitama">
</div>
<div id="chart"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script>
<script src="createVBarChart.js"></script>
<script>
!(function(){
"use strict";
const dataSet = [
{name:"tokyo", value:"100"},
{name:"gunma", value:"200"},
{name:"saitama", value:"300"}
]
//バーチャートモジュールを生成
const BarChart = createVBarChart()
.margin({top:40, left:100, bottom:40, right:10})
.x(function(d){ return d["name"] })
.y(function(d){ return d["value"] })
//チャートを描画する
const selector = d3.selectAll("#chart").datum(dataSet)
.call(BarChart)
//データセットの内容が変更されたらチャートをアップデートする
const proxyHandler = {
get: function(target, name, value){
return target[name]
},
set: function(target, name, value){
if (name == "value" && isNaN(+value)) throw new TypeError("型がちがうよ")
target[name] = value
selector.update(dataSet) //チャートアップデート
return target[name]
}
}
//データセットをネストし、proxyで包む
const nested = d3.nest()
.rollup(function(d){ return new Proxy(d[0], proxyHandler) })
.key(function(d){ return d.name })
.map(dataSet)
//テキストボックスの値が変更されたらデータセットをporxy経由で変更する
const updateDataset = function() {
nested["$"+this.name].value = this.value
}
const textBox = d3.selectAll(".input")
.on("change", updateDataset)
//テキストボックスに初期値を渡す。
textBox.each(function(){
this.value = nested["$"+this.name].value
})
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment