D3多轴插补

时间:2015-08-27 23:41:09

标签: javascript d3.js

我正在开发一个项目来绘制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

enter image description here idx表示数据到达的顺序。

这就是我得到的

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>

enter image description here

取消勾选'文本不是问题,但由于值的多重性,我在插值方面遇到问题:  例如宽度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];
        }

    }

有人可以告诉我如何正确使用轴蜱来达到这种目的吗?

提前谢谢

1 个答案:

答案 0 :(得分:1)

我创建了自己的类/对象,因为d3显然不适用于这种图形

&#13;
&#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);

                        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;
&#13;
&#13;

用法:

&#13;
&#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;
&#13;
&#13;