score:1

Accepted answer

Your data structure is an array of objects, each one containing an inner array with the real coordinates for the circles. Therefore, that single enter selection will not work.

With minimal refactoring, my solution here is appending groups according to the objects, and then, for each one, appending circles according to the inner arrays. For that cumbersome yScale to work you cannot rely on the circle's indices anymore, so I'm using a local variable here:

var pointsGroup = g.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .attr("fill", function(d, i) {
    local.set(this, yScale[i])
    return colors[i];
  });

var points = pointsGroup.selectAll(null)
  .data(function(d) {
    return d.data
  })
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return x(d.x)
  })
  .attr("cy", function(d, i) {
    return local.get(this)(d.y);
  })
  //etc...

Here is the code with those changes:

var local = d3.local();
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
  [0, 10, 20, 30, 40],
  [0, 200, 300, 400, 500]
];
var dataset = [
  [{
    x: 0,
    y: 0
  }, {
    x: 10,
    y: 10
  }, {
    x: 20,
    y: 20
  }, {
    x: 30,
    y: 30
  }, {
    x: 40,
    y: 40
  }],
  [{
    x: 0,
    y: 0
  }, {
    x: 10,
    y: 200
  }, {
    x: 20,
    y: 300
  }, {
    x: 30,
    y: 400
  }, {
    x: 40,
    y: 500
  }]
];

var data = [];
for (var i = 0; i < 2; i++) {
  data.push({
    "data": dataset[i],
    "yAxis": i
  })
}
console.log(data);

//after restructuring dataset array
var data = [{
  data: [{
    x: 0,
    y: 0
  }, {
    x: 10,
    y: 10
  }, {
    x: 20,
    y: 20
  }, {
    x: 30,
    y: 30
  }, {
    x: 40,
    y: 40
  }],
  yAxis: 0,
}, {
  data: [{
    x: 0,
    y: 0
  }, {
    x: 10,
    y: 200
  }, {
    x: 20,
    y: 300
  }, {
    x: 30,
    y: 400
  }, {
    x: 40,
    y: 500
  }],
  yAxis: 1,
}];

const margin = {
  left: 20,
  right: 20,
  top: 20,
  bottom: 80
};

const svg = d3.select('svg');
svg.selectAll("*").remove();

const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);

//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');

xAxisG.append('text')
  .attr('class', 'axis-label')
  .attr('x', width / 3)
  .attr('y', -10)
  .style('fill', 'black')
  .text(function(d) {
    return "X Axis";
  });

yAxisG.append('text')
  .attr('class', 'axis-label')
  .attr('id', 'yAxisLabel0')
  .attr('x', -height / 2)
  .attr('y', -15)
  .attr('transform', `rotate(-90)`)
  .style('text-anchor', 'middle')
  .style('fill', 'black')
  .text(function(d) {
    return "Y Axis 1";
  });

// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
  .domain([0, d3.max(xValueArray)])
  .range([0, width])
  .nice();

var yScale = new Array();
for (var i = 0; i < 2; i++) {
  // interpolator for Y axis -- inner plot region
  var y = d3.scaleLinear()
    .domain([0, d3.max(arr[i])])
    .range([0, height])
    .nice();
  yScale.push(y);
}

const xAxis = d3.axisTop()
  .scale(x)
  .ticks(5)
  .tickPadding(2)
  .tickSize(-height)

const yAxis = d3.axisLeft()
  .scale(yScale[0])
  .ticks(5)
  .tickPadding(2)
  .tickSize(-width);

yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
  var yAxisSecondary = d3.axisLeft()
    .scale(yScale[i])
    .ticks(5)
  yAxisArray.push(yAxisSecondary);
}

svg.append("g")
  .attr("class", "x axis")
  .attr("transform", `translate(80,${height-80})`)
  .call(xAxis);

svg.append("g")
  .attr("class", "y axis")
  .attr("id", "ySecAxis0")
  .attr("transform", "translate(80,20)")
  .call(yAxis);

var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];

//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
  svg.append("g")
    .attr("transform", "translate(" + translation + "," + 20 + ")")
    .attr("id", "ySecAxis" + i)
    .call(yAxisArray[i]);

  yAxisG.append('text')
    .attr('x', -height / 2)
    .attr('y', -60)
    .attr('transform', `rotate(-90)`)
    .attr("id", "yAxisLabel" + i)
    .style('text-anchor', 'middle')
    .style('fill', 'black')
    .text(yLabelArray[i]);

  translation -= 40;
  textTranslation += 40;
}

//************* Mouseover ***************
var tooltip = d3.select("body")
  .append("div")
  .style("opacity", 0)
  .attr("class", "tooltip")
  .style("background-color", "white")
  .style("border", "solid")
  .style("border-width", "1px")
  .style("border-radius", "5px")
  .style("padding", "10px")
  .style("position", "absolute")

var mouseover = function(d) {
  tooltip
    .html("x: " + d.x + "<br/>" + "y: " + d.y)
    .style("opacity", 1)
    .style("left", (d3.mouse(this)[0] + 90) + "px")
    .style("top", (d3.mouse(this)[1]) + "px")
}

// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
  tooltip
    .transition()
    .duration(200)
    .style("opacity", 0)
}

//************* Lines and Data Points ***************
var colors = ["blue", "red"];

var thisScale;

var line = d3.line()
  .x(d => x(d.x))
  .y(d => thisScale(d.y))
  .curve(d3.curveLinear);

var paths = g.selectAll("foo")
  .data(data)
  .enter()
  .append("path");

paths.attr("stroke", function(d, i) {
    return colors[i]
  })
  .attr("d", d => {
    thisScale = yScale[d.yAxis]
    return line(d.data);
  })
  .attr("stroke-width", 2)
  .attr("id", function(d, i) {
    return "line" + i
  })
  .attr("fill", "none");

var pointsGroup = g.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .attr("fill", function(d, i) {
    local.set(this, yScale[i])
    return colors[i];
  });

var points = pointsGroup.selectAll(null)
  .data(function(d) {
    return d.data
  })
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return x(d.x)
  })
  .attr("cy", function(d, i) {
    return local.get(this)(d.y);
  })
  .attr("r", 3)
  .attr("class", function(d, i) {
    return "blackDot" + i
  })
  .attr("clip-path", "url(#clip)")
  .on("mouseover", mouseover)
  .on("mouseleave", mouseleave)

//plot lines (hard-coding)
/*var lineFunction1 = d3.line()
.x(function(d) {
  return x(d.x);
})
.y(function(d) {
  return yScale[0](d.y);
})
.curve(d3.curveLinear);

var lineFunction2 = d3.line()
.x(function(d) {
  return x(d.x);
})
.y(function(d) {
  return yScale[1](d.y);
})
.curve(d3.curveLinear);

var path1 = g.append("path")
.attr("class", "path" + 0)
.attr("id", "line" + 0)
.attr("d", lineFunction1(data[0]))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");

var path2 = g.append("path")
.attr("class", "path" + 1)
.attr("id", "line" + 1)
.attr("d", lineFunction2(data[1]))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");*/

//plot lines and points using for loop
/*for (var i = 0; i < 2; i++) {
  var lineFunction = d3.line()
  .x(function(d) {
    return x(d.x);
  })
  .y(function(d) {
    return yScale[i](d.y);
  })
  .curve(d3.curveLinear);

  var paths = g.append("path")
  .attr("class", "path" + i)
  .attr("id", "line" + i)
  .attr("d", lineFunction(data[i]))
  .attr("stroke", colors[i])
  .attr("stroke-width", 2)
  .attr("fill", "none")
  .attr("clip-path", "url(#clip)")

  //plot a circle at each data point
  g.selectAll(".dot")
    .data(data[i])
    .enter().append("circle")
    .attr("cx", function(d) { return x(d.x)} )
    .attr("cy", function(d) { return yScale[i](d.y); } )
    .attr("r", 3)
    .attr("class", "blackDot" + i)
    .attr("clip-path", "url(#clip)")
    .on("mouseover", mouseover)
    .on("mouseleave", mouseleave)
}*/

//************* Legend ***************
var legend = svg.selectAll(".legend")
  .data(data)
  .enter().append("g")

legend.append("rect")
  .attr("x", width + 65)
  .attr("y", function(d, i) {
    return 30 + i * 20;
  })
  .attr("width", 18)
  .attr("height", 4)
  .style("fill", function(d, i) {
    return colors[i];
  })

legend.append("text")
  .attr("x", width + 60)
  .attr("y", function(d, i) {
    return 30 + i * 20;
  })
  .attr("dy", ".35em")
  .style("text-anchor", "end")
  .text(function(d, i) {
    return "Value" + (i + 1);
  })
  .on("click", function(d, i) {
    // Determine if current line is visible
    let opacity = d3.select("#line" + i).style("opacity");
    let newOpacity;
    if (opacity == 0) {
      newOpacity = 1;
    } else {
      newOpacity = 0
    }
    d3.select("#line" + i).style("opacity", newOpacity);
    d3.selectAll(".blackDot" + i).style("opacity", newOpacity);
    d3.select("#ySecAxis" + i).style("opacity", newOpacity);
    d3.select("#yAxisLabel" + i).style("opacity", newOpacity);
  });

//************* Zoom & Brush***************
const margin2 = {
  left: 80,
  right: 0,
  top: 80,
  bottom: 0
};
const height2 = height - margin2.top - margin2.bottom;
var xZoom = d3.scaleLinear().range([0, width]);
var yZoom = d3.scaleLinear().range([height2, 0]);

var xAxis2 = d3.axisTop(xZoom);

var brush = d3.brushX()
  .extent([
    [0, 0],
    [width, height2]
  ])
  .on("brush end", brushed);

var zoom = d3.zoom()
  .scaleExtent([1, Infinity])
  .translateExtent([
    [0, 0],
    [width, height]
  ])
  .extent([
    [0, 0],
    [width, height]
  ])
  .on("zoom", zoomed);

var clip = svg.append("defs").append("svg:clipPath")
  .attr("id", "clip")
  .append("svg:rect")
  .attr("width", width)
  .attr("height", height)
  .attr("x", 0)
  .attr("y", 0);

xZoom.domain(x.domain());
yZoom.domain(y.domain());

var context = svg.append("g")
  .attr("class", "context")
  .attr("transform", "translate(" + margin2.left + "," + 125 + ")");

context.append("g")
  .attr("class", "axis axis--x")
  .attr("transform", "translate(0," + height2 + ")")
  .call(xAxis2);

context.append("g")
  .attr("class", "brush")
  .call(brush)
  .call(brush.move, x.range());

function brushed() {
  if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
  var s = d3.event.selection || xZoom.range();
  x.domain(s.map(xZoom.invert, xZoom));
  svg.select(".x.axis").call(xAxis);
  //svg.select(".path0").attr("d", lineFunction1(data[0]));
  //svg.select(".path1").attr("d", lineFunction2(data[1]));
  for (var i = 0; i < 2; i++) {
    //svg.select(".path" + i).attr("d", lineFunction(data[i]));
    g.selectAll(".blackDot" + i)
      .attr("cx", function(d) {
        return x(d.x);
      })
      .attr("cy", function(d) {
        return yScale[i](d.y);
      })
      .attr("r", 3)
  }
}

function zoomed() {
  if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return;
  var t = d3.event.transform;
  x.domain(t.rescaleX(xZoom).domain());
  svg.select(".x.axis").transiton(t).call(xAxis);
  //svg.select(".path0").transiton(t).attr("d", lineFunction1(data[0]));
  //svg.select(".path1").transiton(t).attr("d", lineFunction2(data[1]));
  for (var i = 0; i < 2; i++) {
    //svg.select(".path" + i).attr("d", lineFunction(data[i]));
    g.selectAll(".blackDot" + i)
      .attr("cx", function(d) {
        return x(d.x);
      })
      .attr("cy", function(d) {
        return yScale[i](d.y);
      })
      .attr("r", 3)
  }
}
.xy_chart {
  position: relative;
  left: 70px;
  top: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>

Pay attention to the fact that one of the circles has an incorrect cy value. So, I'd suggest you to change your y scale approach.


Related Query

More Query from same tag