D3.js multiple y-Axis with same position of ticks?
Asked Answered
E

1

7

I have chart with 3 y-axis and one x-axis and must zoom all 3 axis.

! DEMO


what I have

Is it possible to set all 3 y-axis with center on 0? And if I zoom chart ticks must stay on same place?

Or, maybe, say "show always only 10 ticks between 0-value". For example:
Range 1 -> [10..0..-10]
Range 2 -> [350..0..-350]
Range 3 -> [0,1..0..0,9]

But all ticks from all 3 ranges must stay in same position.

need same view


UPDATE

//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));
  }
}

Update v3:

enter image description here


Update v4:

enter image description here

Is it possible to set padding between ticks on y-axis? Or better say "disable distance between ticks, and set it with fixed value"

.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()

UPDATE v5:

This is what I need:

enter image description here

I created this 3 y-axes without d3.axis.***, but this solution is very bad :) and I will use d3-API for my Chart!

Please help me!!!!


Edva answered 15/12, 2015 at 14:38 Comment(9)
You should get this automatically if you adjust the domain of all three axes in the zoom handler.Devora
@LarsKotthoff: But all y-Axes have different domains...Edva
You should be able to translate between them -- take the extents of the brush, translate to screen coordinates with the scale linked to the brush, then invert those screen coordinates on the other two scales. This will give you the new domains for those scales.Devora
@LarsKotthoff: but in this case I have again different arrays...Edva
I don't know what you mean by that. What arrays?Devora
@ElderovAli it would be nice if you can share your code in a fiddle along with the dataset. That would help us nail it.Keeler
@Cyril: I added demo-link in my question.Edva
@LarsKotthoff: See my Update4 and demoEdva
With all these edits, the question has gotten extremely confusing. Could you collapse the edits by removing the parts that are no longer relevant and condensing the question?Zinovievsk
B
7

Here is your code modified to get the ticks to line up at 0. A few changes to note are annotated in the code by:

// ***

The main change is that your domain should be centered at zero if you want the ticks to line up at zero and have the same number of ticks above and below zero on all three axes.

I regenerated the tick values on every zoom/pan, showing the min, max and equally spaced values. You may want to do some rounding, or force zero to be a tick value if it is in the visible domain.

<!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>
Baba answered 19/12, 2015 at 13:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.