我正在开发一个项目来绘制csv / Json数据流(条形图),其中数据的到达顺序很重要。 Y轴是唯一的,但有多个X轴对应于不同的数据测量值。 鉴于以下数据,我无法生成看起来像这样的漂亮图:
x0,x1,x2,y,idx
-1,z,w2,10,0
0,z,w2,9,1
1,z,w2,8,2
-1,k,w2,11,3
0,k,5q,5,4
1,k,5q,8,5
这就是我得到的
X=["idx","x0","x1","x2"];
Y=["y"];
var margin = {
top: 80,
right: 180,
bottom: 180,
left: 180
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = [],
x = [];
var x_uid = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
for (var idx = 0; idx < X.length; idx++) {
x[idx] = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
xAxis[idx] = d3.svg.axis()
.scale(x[idx])
.orient("bottom");
}
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// .ticks(8, "%");
var svg = d3.select("body").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 + ")");
var data = [{
x0:-1,
x1:z,
x2:w2,
y:10,
idx:0
},
{
x0:0,
x1:z,
x2:w2,
y:10,
idx:1
},
{
x0:1,
x1:z,
x2:w2,
y:10,
idx:2
},
{
x0:-1,
x1:j,
x2:w2,
y:10,
idx:3
},
{
x0:0,
x1:j,
x2:5q,
y:10,
idx:4
},
{
x0:1,
x1:j,
x2:5q,
y:10,
idx:5
}]
if(data) {
for (var idx = 0; idx < X.length; idx++) {
x[idx].domain(data.map(function(d) {
return d[X[idx]];
}));
}
x_uid.domain(data.map(function(d) {
return d.idx;
}));
y.domain([0, d3.max(data, function(d) {
d.value = d[Y[0]];
return d.value;
})]);
for (var idx = 0; idx < X.length; idx++)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + idx * 25) + ")")
.call(xAxis[idx]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x_uid(d.idx);
})
.attr("width", 1)
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js"></script>
<div id="chart"></div>
取消勾选'文本不是问题,但由于值的多重性,我在插值方面遇到问题: 例如宽度w2>宽度为5q 例如x0轴应为-1 0 1 -1 0 1,但d3插值为-1 0 1 我尝试使用rangeRoundBand而不是rangeRoundPoint,但问题类似。 我也试过玩tickValues但无济于事。 我尝试使用线性比例而不是序数进行自己的插值,但是它很快变得非常混乱,因为它会强制我手动计算和调整所有刻度的位置和文本,同时考虑到d3.behavior缩放级别等...
function adjustTickPosition(selection, count, scale, translate, rotate) {
//selection = axis
//count = multiplicity of each tick
//scale = d3.behavior.zoom scale
//translate = d3.behavior.zoom translation
//rotate = irrelevent here (additional styling)
console.info( selection.selectAll("g.tick"))
// cancel previous position
//
// /!\ For some reason there is always 100 ticks instead of the appropriate number
//
selection.selectAll("g.tick")
.attr("transform", "translate(0,0)");
// align tick marks
selection.selectAll("g.tick line")
.attr('transform', function (d, k) {
if (k <= count.length - 1) {
var newPosition = scaleTranslate(count[k]);
if (newPosition > width || newPosition < 0) {
d3.select(this.parentNode).style("visibility", "hidden");
} else
d3.select(this.parentNode).style("visibility", "visible");
return 'translate(' + newPosition + ',0)';
} else
return 'translate(0,0)';
});
// offset tick label compared to tick marks
selection.selectAll("g.tick text")
.attr('transform', function (d, k) {
if (k <= count.length - 1) {
var pos, transform;
if (k > 0) pos = (count[k - 1] + count[k]) / 2;
else pos = count[k] / 2;
var newPosition = scaleTranslate(pos);
if (newPosition > width || newPosition < 0) {
d3.select(this.parentNode).style("visibility", "hidden");
} else
d3.select(this.parentNode).style("visibility", "visible");
var transform = 'translate(' + newPosition + ',0)';
if (rotate) transform += ' rotate(-65)';
return transform;
} else
return 'translate(0,0)';
});
if (rotate) selection.selectAll("g.tick text").style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em");
return selection;
function scaleTranslate(v) {
return v / count[count.length - 1] * width * scale + translate[0];
}
}
有人可以告诉我如何正确使用轴蜱来达到这种目的吗?
提前谢谢
答案 0 :(得分:1)
我创建了自己的类/对象,因为d3显然不适用于这种图形
function chartAxis(key, args) {
//***************************
// PRIVATE
//***************************
var _direction = args ? (args.direction || "x") : "x";
var _width = args ? (args.width || 500) : 500;
var _alignTicks = args ? (args.alignTicks || false) : false;
var _tickSize = args ? (args.tickSize || 0) : 0;
var _numTicks = args ? (args.numTicks || 10) : 10;
var _offset = args ? (args.offset || 25) : 25;
var _zoom = args ? (args.zoom || {
s: 1,
t: 0
}) : {
s: 1,
t: 0
};
var _totalLength;
function consecutiveReduction(list, key) {
var Bin = function (val, cnt) {
return {
value: val,
count: cnt,
cumulativeCount: 0,
center: 0,
position: 0
};
};
var result = list.map(function (d) {
return key ? d[key] : d;
}).reduce(function (acc, d) {
var currentBin = acc[acc.length - 1];
if ((acc.length > 0) && d === currentBin.value) {
//add to current bin
currentBin.count++;
} else {
//create new bin
acc.push(new Bin(d, 1));
}
return acc;
}, []);
result.forEach(accumulate);
result.forEach(positionTick);
return result;
}
function positionTick(d) {
d.position = ApplyZoom(d.cumulativeCount);
d.center = _alignTicks ? d.position : ApplyZoom(d.cumulativeCount - d.count / 2);
function ApplyZoom(val) {
var translate;
if (_zoom.t.length > 1)
translate = (_direction == "x") ? _zoom.t[0] : _zoom.t[1];
else
translate = _zoom.t;
return val / _totalLength * _width * _zoom.s + translate;
}
}
function accumulate(d, i, arr) {
d.cumulativeCount = d.count;
if (i > 0) d.cumulativeCount += arr[i - 1].cumulativeCount;
}
//***************************
// PUBLIC
//***************************
var xAxis = function (selection) {
selection.each(function (data) {
// calculate
_totalLength = data.length;
var tickData = consecutiveReduction(data, key);
console.log(tickData.map(function (d) {
return d.count
}))
console.table(data,key)
//create parent axis with clip-path
var axis = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", 0)
.attr("y", _offset - _tickSize)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
var axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: 0,
y1: _offset,
x2: _width,
y2: _offset,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _width + 10,
y: _offset
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function (d) {
return d.position
})
.attr("x2", function (d) {
return d.position
})
.attr("y1", function (d) {
return _offset - _tickSize
})
.attr("y2", function (d) {
return _offset + 5
});
ticks.select(".tick text")
.text(function (d) {
return d.value;
})
.attr("x", function (d) {
return d.center;
})
.attr("y", function (d) {
return _offset + 10;
})
.style("text-anchor", "middle")
.style("text-length", function (d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
})
};
var yAxis = function (selection) {
selection.each(function (data) {
// calculate
_totalLength = data.length;
var tickData = d3.extent(data, function (d) {
return d[key];
});
var tickRange = (tickData[1] - tickData[0]) / (_numTicks - 4 + 1); // -4 -> [0.85*min min ... max 1.15*max]
console.log(tickData.map(function (d) {
return d.count
}))
console.log(_tickSize)
//create parent axis with clip-path
var axis = axisLine = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", _offset)
.attr("y", 0)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: _offset,
y1: 0,
x2: _offset,
y2: _width,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _offset,
y: -10
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function (d) {
return _offset - 5
})
.attr("x2", function (d) {
return _offset + _tickSize
})
.attr("y1", function (d) {
return d.position
})
.attr("y2", function (d) {
return d.position
});
ticks.select(".tick text")
.text(function (d) {
return d.value;
})
.attr("x", function (d) {
return _offset + 10;
})
.attr("y", function (d) {
return d.center;
})
.style("text-anchor", "middle")
.style("text-length", function (d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
}); // end select.foreach
}; // end yAxis
xAxis.BindToZoom = function (zoomObject) {
_zoom = zoomObject;
return xAxis;
}
yAxis.BindToZoom = function (zoomObject) {
_zoom = zoomObject;
return yAxis;
}
return (_direction == "x") ? xAxis : yAxis;
}
&#13;
用法:
function chartAxis(key, args) {
//***************************
// PRIVATE
//***************************
var _direction = args ? (args.direction || "x") : "x";
var _width = args ? (args.width || 500) : 500;
var _alignTicks = args ? (args.alignTicks || false) : false;
var _tickSize = args ? (args.tickSize || 0) : 0;
var _numTicks = args ? (args.numTicks || 10) : 10;
var _offset = args ? (args.offset || 25) : 25;
var _zoom = args ? (args.zoom || {
s: 1,
t: 0
}) : {
s: 1,
t: 0
};
var _totalLength;
function consecutiveReduction(list, key) {
var Bin = function(val, cnt) {
return {
value: val,
count: cnt,
cumulativeCount: 0,
center: 0,
position: 0
};
};
var result = list.map(function(d) {
return key ? d[key] : d;
}).reduce(function(acc, d) {
var currentBin = acc[acc.length - 1];
if ((acc.length > 0) && d === currentBin.value) {
//add to current bin
currentBin.count++;
} else {
//create new bin
acc.push(new Bin(d, 1));
}
return acc;
}, []);
result.forEach(accumulate);
result.forEach(positionTick);
return result;
}
function positionTick(d) {
d.position = ApplyZoom(d.cumulativeCount);
d.center = _alignTicks ? d.position : ApplyZoom(d.cumulativeCount - d.count / 2);
function ApplyZoom(val) {
var translate;
if (_zoom.t.length > 1)
translate = (_direction == "x") ? _zoom.t[0] : _zoom.t[1];
else
translate = _zoom.t;
return val / _totalLength * _width * _zoom.s + translate;
}
}
function accumulate(d, i, arr) {
d.cumulativeCount = d.count;
if (i > 0) d.cumulativeCount += arr[i - 1].cumulativeCount;
}
//***************************
// PUBLIC
//***************************
var xAxis = function(selection) {
selection.each(function(data) {
// calculate
_totalLength = data.length;
var tickData = consecutiveReduction(data, key);
//create parent axis with clip-path
var axis = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", 0)
.attr("y", _offset - _tickSize)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
var axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: 0,
y1: _offset,
x2: _width,
y2: _offset,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _width + 10,
y: _offset
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function(d) {
return d.position
})
.attr("x2", function(d) {
return d.position
})
.attr("y1", function(d) {
return _offset - _tickSize
})
.attr("y2", function(d) {
return _offset + 5
});
ticks.select(".tick text")
.text(function(d) {
return d.value;
})
.attr("x", function(d) {
return d.center;
})
.attr("y", function(d) {
return _offset + 10;
})
.style("text-anchor", "middle")
.style("text-length", function(d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
})
};
var yAxis = function(selection) {
selection.each(function(data) {
// calculate
_totalLength = data.length;
var tickData = consecutiveReduction(data, key);
//create parent axis with clip-path
var axis = axisLine = d3.select(this)
.attr("id", key);
axis.selectAll("#clipAxis-" + key).data([1]).enter()
.append("clipPath")
.attr("id", "clipAxis-" + key)
.append("svg:rect")
.attr("x", _offset)
.attr("y", 0)
.attr("width", _width)
.attr("height", 25 + _tickSize);
// Axis line and label
axisLine = axis.selectAll(".axisLine").data([1]).enter();
axisLine.append("line").attr({
x1: _offset,
y1: 0,
x2: _offset,
y2: _width,
class: "axisLine"
});
axisLine.append("text")
.text(key)
.attr({
x: _offset,
y: -10
}).style("text-anchor", "start");
// tick on the axis
var ticks = axis.selectAll("g.tick")
.data(tickData);
// ENTER
var newticks = ticks.enter().append("g").attr("class", "tick");
newticks.append("line");
newticks.append("text");
// UPDATE
ticks.attr("clip-path", "url(#clipAxis-" + key + ")");
ticks.select(".tick line")
.attr("x1", function(d) {
return _offset - 5
})
.attr("x2", function(d) {
return _offset + _tickSize
})
.attr("y1", function(d) {
return d.position
})
.attr("y2", function(d) {
return d.position
});
ticks.select(".tick text")
.text(function(d) {
return d.value;
})
.attr("x", function(d) {
return _offset + 10;
})
.attr("y", function(d) {
return d.center;
})
.style("text-anchor", "middle")
.style("text-length", function(d) {
return (0.6 * 2 * (d.position - d.center)) + "px";
});
// EXIT
ticks.exit().remove();
}); // end select.foreach
}; // end yAxis
xAxis.BindToZoom = function(zoomObject) {
_zoom = zoomObject;
return xAxis;
}
yAxis.BindToZoom = function(zoomObject) {
_zoom = zoomObject;
return yAxis;
}
return (_direction == "x") ? xAxis : yAxis;
}
var data = [{
"a": 1,
"b": 3,
c: 1
}, {
"a": 1,
"b": 3,
c: 2
}, {
"a": 1,
"b": 2,
c: 3
}, {
"a": 1,
"b": 3,
c: 4
}, {
"a": 2,
"b": 3,
c: 5
}, {
"a": 3,
"b": "a",
c: 6
}, {
"a": 1,
"b": "a",
c: 7
}];
X = ["b", "a", "c"];
var axesDOM = d3.select("svg")
.selectAll(".axis")
.data(X).enter()
.append("g").attr("class", "axis");
axesDOM.each(function(x, i) {
d3.select(this).datum(data)
.call(new chartAxis(x, {
width: 200,
offset: 25 + i * 25,
direction: "x"
}));
});
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="200px" height="200px"></svg>
&#13;