D3.js多个y轴具有相同的刻度位置?

时间:2015-12-15 14:38:22

标签: javascript d3.js charts nvd3.js

我的图表有3个y轴和一个x轴,必须缩放所有3个轴。

  

!   的 DEMO

what I have

是否可以设置所有3 y轴,中心为0?如果我缩放图表刻度必须保持在同一个地方?

或者,也许,说"在0值和#34之间始终只显示10个刻度。 例如:
    范围1 - > [10..0 ..- 10]
    范围2 - > [350..0 ..- 350]
    范围3 - > [0,1..0..0,9]

但是所有3个范围的所有滴答必须保持在同一位置。

need same view

更新

//zooming
var zoom0 = d3.behavior.zoom().x(x).y(_y[0]).on("zoom", zoomed);
var zoom1 = d3.behavior.zoom().y(_y[1]);
var zoom2 = d3.behavior.zoom().y(_y[2]);
***
function zoomed() {
  zoom1.scale(zoom0.scale());
  zoom1.translate(zoom0.translate());
  zoom2.scale(zoom0.scale());
  zoom2.translate(zoom0.translate());
  graph.select(".x.axis").call(xAxis);
  for (var i = 0; i < _y.length; i++) {
    graph.select(".y.ax" + i).call(_yAxis[i]);
    graph.select('.line.ax' + i).attr('d', _lineGenerator(i));
  }
}

更新v3:

enter image description here

更新第4版:

enter image description here

是否可以在y轴上的刻度之间设置填充?或者更好地说&#34;禁用刻度线之间的距离,并将其设置为固定值&#34;

.tickValues() - doesn't help in this case...
function create_Y_axes() {
  var _key, _currentKeyIndex, yDomain_before, yDomain_after,  yD_min,  yD_max,  _index;

  for (var i = 0; i < series.length; i++) {

    _key = series[i]["unit"];
    _currentKeyIndex = yCategories.indexOf(_key);

    if (_currentKeyIndex === -1) {
      //If key not found in array
      yCategories.push(_key);

      series[i]["yAxis"] = yCategories.length - 1;

      _y.push(
        d3.scale.linear()
          .domain(d3.extent(series[i]["UNIT_DATA"], function(d,i){return d.wert;}))
          .range([chartHeight, 0])
          .nice()
      );

    } else {
      series[i]["yAxis"] = _currentKeyIndex;
    }
  } //for

  var yDomain,min, max, ticks = 10, r1,r2,range,stepL,stepR, t, tArray, h;

  for (var i = 0; i < _y.length; i++) {

    yDomain = _y[i].domain();// [-60, 60]

    min = yDomain[0];// -60
    max = yDomain[1];//  60

    h = Math.floor((ticks / 2));

    stepL = (Math.abs(min / h));
    stepR = (Math.abs(max / h));

    r1 = d3.range(min, 0 - stepL, stepL);
    r2 = d3.range(0, max + stepR, stepR);

    tArray = d3.merge([r1, r2]);

    t = d3.scale.ordinal().domain(tArray).rangePoints([chartHeight, 0]).domain();
      _yAxis.push(
        d3.svg.axis()
              .scale(_y[i])
              .orient("left")
              .tickPadding(5)
              .tickValues(t)
              .tickFormat(function(d){return d.toFixed(2);})
      );

  } 

}// createYaxes()

更新v5:

这就是我需要的:

enter image description here

我在没有d3.axis。***的情况下创建了这3个y轴,但是这个解决方案非常糟糕:)我会在我的图表中使用d3-API!

请帮帮我!!!!

1 个答案:

答案 0 :(得分:5)

以下是您修改的代码,以便将标记排列为0.需要注意的一些更改将在代码中注释:

  

// ***

主要变化是,如果您希望刻度线在零处排列并且在所有三个轴上具有相同的刻度数高于和低于零,则您的域应该以零为中心。

我在每个缩放/平移上重新生成刻度值,显示最小值,最大值和等间距值。您可能想要进行一些舍入,或者如果它在可见域中,则强制零为刻度值。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet prefetch" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  <style>
body {
  background: #fff;
  text-align: center;
  position: relative;
}

.row {
  margin-top: 25px;
  margin-bottom: 25px;
}

.tooltipX {
  position: absolute;
  text-align: center;
  padding: 10px;
  border-radius: 15px;
  border: 1px solid #000;
  margin-top: -20px;
  font: 10px sans-serif;
  background: rgba(0, 0, 0, 0.1);
  pointer-events: none;
  display: block;
}

svg {
  background: #e8e8e8;
}

svg .title {
  font-size: 16px;
  fill: #000;
  font-weight: 700;
}

svg .subtitle {
  font-size: 12px;
  font-weight: 500;
  fill: #000;
  stroke-width: 1px;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

svg .axis {
  fill: #000;
  pointer-events: all;
}

svg .axis text {
  font-family: sans-serif;
  font-size: 11px;
}

svg .axis path {
  fill: none;
  stroke-width: 1;
  stroke: #000;
  shape-rendering: crispEdges;
}

svg .axis line {
  fill: none;
  stroke-width: 1;
  stroke: #000;
  shape-rendering: crispEdges;
}

svg .line {
  stroke-width: 1;
  fill: none;
}

svg .grid .tick {
  stroke-width: 1;
  stroke: rgba(0, 0, 0, 0.1);
}

svg .legend {
  cursor: pointer;
}

svg .overlay {
  fill: none;
  pointer-events: all;
}

svg .x.axis .tick line {
  stroke: rgba(0, 0, 0, 0.1);
}

svg .y.axis .tick line {
  stroke: #f00;
}
  </style>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://a4a42cb81401f26d4437b778613cb8ba7c3b1b73.googledrive.com/host/0B0gcn9nZbbFgM3MtZFRGeTJVWFU"></script>
<div class="container">
  <div class="row">
<div class="col-xs-12">
  <div id="chart"></div>
</div>
  </div>
</div>
<script>

  (function (window) {
'use strict';

var TICKS = 10;

//--------------------------------------------------
//Generate D3 random color
var colorscale = d3.scale.category20();

var chart = {
  "options": {
    "chart": {
      "title": "",
      "description": ""
    }
  },
  "legend": {
    "enable": true,
    "useName": true
  },
  "show": {
    "title": true,
    "description": true,
    "yGrid": true
  },
  "zoom": {
    "enable": true,
    "resetBtnId": "#btnResetZoom"
  },
  "cursor": {
    "enable": true
  },
  "series": [],
  "yGridBtnId": "#btnYGrid"
};

//prepare options
var options = {
  "chart": {
    "title": "my title",
    "description": "my description"
  }
};

//prepare Series
var series = [
  {
    "channel": {
      "name": "my name",
      "code": "codeXY"
    },
    "stat": {
      "min": -6.99796,
      "minTime": 0.01225,
      "max": 0.110055,
      "maxTime": 0.01755
    },
    "unit": "Bar",
    "UNIT_DATA": data001
  },

  {
    "channel": {
      "name": "my Name 2",
      "code": "codeOP"
    },
    "stat": {
      "min": -310.51,
      "minTime": 0.0472,
      "max": 191.675,
      "maxTime": 0.1282
    },
    "unit": "N",
    "UNIT_DATA": data036
  },

  {
    "channel": {
      "name": "my name 3",
      "code": "coedXP"
    },
    "stat": {
      "min": -30.2723,
      "minTime": 0.05935,
      "max": 0.753309,
      "maxTime": -0.0972
    },
    "unit": "Nm",
    "UNIT_DATA": data080
  }
];

//--------------------------------------------------
//Settings & variables
var _x;
var xAxis;
var _yScale = [];
var _yAxis = [];
var zooming;
var yCategories = [];
var margins = {
    top: 50,
    left: 50,
    bottom: 100,
    right: 50,
    height: 480
  },
  _factor = 50,
  chartWidth = "100%",
  chartHeight = margins.height - margins.top - margins.bottom, graph;

//--------------------------------------------------
//add y-Axis
function create_Y_axes() {
  var _key, _currentKeyIndex, yDomain, yD_max;
  for (var i = 0; i < series.length; i++) {
    _key = series[i]["unit"];
    _currentKeyIndex = yCategories.indexOf(_key);
    if (_currentKeyIndex === -1) {
      //If key not found in array
      yCategories.push(_key);
      series[i]["yAxis"] = yCategories.length - 1;
      yDomain = d3.extent(series[i]["UNIT_DATA"], function (d, i) {
        return d.wert;
      });
      // *** Center the yDomain around 0
      yD_max = d3.max([Math.abs(yDomain[0]), Math.abs(yDomain[1])]);
      yDomain = [-yD_max, +yD_max];
      console.log(i, yCategories[i], yDomain);
      _yScale.push(
        d3.scale.linear()
          .domain(yDomain)
          .range([chartHeight, 0])
          .nice()
      );
    } else {
      series[i]["yAxis"] = _currentKeyIndex;
    }
  } //for

  var tArray, tickValues, ticks = 10;

  for (var i = 0; i < _yScale.length; i++) {

    tArray = generateTicksForYaxis(_yScale[i].domain());// [-60, 60]
    // *** Don't need this
    //tickValues = d3.scale.ordinal().domain(tArray).rangePoints([chartHeight, 0]).domain();
    //console.error(tickValues, tickValues.length);
    //_yScale[i].domain(d3.extent(tickValues));
    console.log(" LINEAR\t", d3.scale.linear().domain(tArray).domain());
    console.log("ORDINAL\t", d3.scale.ordinal().domain(tArray).rangePoints([chartHeight, 0]).domain());


    _yAxis.push(
      d3.svg.axis()
        .scale(_yScale[i])
        .orient("left")
        // *** No need for .ticks if specifying values
        //.ticks(ticks)
        .tickPadding(5)
        .tickValues(tArray)
        .tickFormat(function (d) {
          return d.toFixed(2);
        })
    );

  }

}// createYaxes()

function generateTicksForYaxis(yDomain) {
  // **** If the domain is centered around 0, we can just divide it into TICKS steps.
  var min = yDomain[0];
  var max = yDomain[1];
  var step = (max - min)/ TICKS;
  console.log("yDomain:", yDomain, "min", min, "max", max, "step:", step);
  var tickArray = d3.range(min, max+step, step);
  console.log("Range[", tickArray.length, "]:", tickArray);
  return tickArray;
}

//--------------------------------------------------
//add yAxes
create_Y_axes();

//update chart width!!!
chartWidth = d3.select("#chart").node().getBoundingClientRect().width - (_factor * _yScale.length) - margins.right;
create_X_axis();

//--------------------------------------------------
//SVG container
graph = d3
  .select("#chart")
  .append("svg:svg")
  .attr("width", "100%")
  .attr("height", chartHeight + margins.top + margins.bottom)
  .append("g") //append new container element and move it with margin top/left
  .attr("transform", "translate(" + (_factor * _yScale.length) + "," + margins.top + ")");

//--------------------------------------------------

//Redraw the axes
graph.selectAll('g.axis').remove();

//draw x-axis
graph
  .append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0, " + chartHeight + ")")
  .call(xAxis)
  .append("text")
  .attr("class", "x-text")
  .attr("x", chartWidth / 2)
  .attr("y", 25)
  .text("Sec.");

//draw y-axis
console.log("draw y-axes", _yScale.length, _yAxis.length, yCategories);
for (var i = 0; i < _yScale.length; i++) {
  graph
    .append("g")
    .attr("class", "y axis ax" + i)
    .attr("transform", "translate(-" + (i * 50) + ",0)")
    .call(_yAxis[i])
    .append("text")
    .attr("y", -10)
    .attr("x", -10)
    .style('text-anchor', 'end')
    .text(yCategories[i]);
}

// Add clip-zone
graph.append("clipPath").attr("id", "clip").append("rect").attr("class", "plot").attr("width", chartWidth).attr("height", chartHeight);

function activateZoom() {
  if (chart.zoom.enable) {
    var yID = null;
    zooming = [];
    for (var i = 0; i < series.length; i++) {
      yID = series[i]["yAxis"];
      zooming.push(d3.behavior.zoom().x(_x).y(_yScale[yID]));
    }
    zooming[0].on("zoom", zoomed);
    graph.call(zooming[0]);
    d3.select(chart.zoom.resetBtnId).on("click", resetZoom);
  }
} //activateZoom()

// *** On zoom display min, max and equally spaced values.
// You may want to force zero to display if it is present.
function reCalculateTickValues(yAxis, yScale) {
  var min = yScale.invert(chartHeight);
  var max = yScale.invert(0);
  var step = (Math.abs(max) + Math.abs(min))/ TICKS;
  var tickValues = d3.range(min, max, step);
  tickValues.push(max);
  yAxis.tickValues(tickValues);
}

function zoomed() {
  graph.select(".x.axis").call(xAxis);
  for (var i = 1; i < series.length; i++) {
    zooming[i].scale(zooming[0].scale());
    zooming[i].translate(zooming[0].translate());
  }

  for (var i = 0; i < series.length; i++) {
    graph.select('.line.ax' + i).attr('d', _lineGenerator(i));
  }

  for (var i = 0; i < _yScale.length; i++) {
    // ** Recalculate the tickValues so they don't go outside the axis
    reCalculateTickValues(_yAxis[i], _yScale[i]);
    graph.select(".y.ax" + i).transition().duration(1000).call(_yAxis[i]);
  }

}//zoomed()

function resetZoom() {
  //console.log("reset()");

  graph.call(
    zooming[0]
      .x(
      _x.domain(d3.extent(series[0]["UNIT_DATA"], function (d) {
        return d.zeit;
      }))
      //.nice()
    )
      .y(
      _yScale[0]
        .domain(d3.extent(series[0]["UNIT_DATA"], function (d) {
          return d.wert;
        }))
      //.nice()
    )
      .event
  );
}//reset


function getChannelName(channelObj) {
  return (chart.legend.useName) ? channelObj.name : channelObj.code;
}


function drawLines() {
  //console.log("drawLines()");

  series.forEach(function (d, i) {

    graph
      .append("path")
      .attr("d", _lineGenerator(i))
      .attr("stroke", colorscale(i))
      .attr("class", "line ax" + i)
      .attr("id", "channel_" + d.channel.code)
      .attr("clip-path", "url(#clip)")
      .on("click", function (d) {
        console.log("click on line:", d3.select(this).attr("id"));
      });

  });


}// drawLines()

// Draw lines
/**
 * @param index -
 *          index in array of series
 */
function _lineGenerator(index) {
  var yId = series[index]["yAxis"];
  //console.log("lineGenerator() ->", "index:", index, "yId:", yId);

  return d3.svg
    .line()
    .x(function (d) {
      return _x(d.zeit);
    })
    .y(function (d) {
      return _yScale[yId](d.wert);
    })(series[index]["UNIT_DATA"]);
}

//--------------------------------------------------

function create_X_axis() {
  _x = d3.scale
    .linear()
    .range([0, chartWidth])
    .domain(d3.extent(series[0]["UNIT_DATA"], function (d) {
      return d.zeit;
    }))
    .nice()
    //.clamp(true)
  ;

  xAxis = d3.svg.axis().scale(_x).orient("bottom").ticks(10).tickSize(-chartHeight);
}

activateZoom();

drawLines();

  })(window);

</script>
</body>
</html>