Skip to content Skip to sidebar Skip to footer

How To Plot Data Points On Multi-line Chart With Multiple Y Axes

I am trying to add data points to my line chart with multiple y axes. Click here for my fiddle. //after restructuring dataset array var data = [{ data: [{ x:

Solution 1:

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 arrayvar 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 regionvar x = d3.scaleLinear()
  .domain([0, d3.max(xValueArray)])
  .range([0, width])
  .nice();

var yScale = newArray();
for (var i = 0; i < 2; i++) {
  // interpolator for Y axis -- inner plot regionvar 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 = newArray();
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 plottedfor (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 againvar 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]
    returnline(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) {
    returnx(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) {
    return30 + 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) {
    return30 + 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 visiblelet 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());

functionbrushed() {
  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) {
        returnx(d.x);
      })
      .attr("cy", function(d) {
        return yScale[i](d.y);
      })
      .attr("r", 3)
  }
}

functionzoomed() {
  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) {
        returnx(d.x);
      })
      .attr("cy", function(d) {
        return yScale[i](d.y);
      })
      .attr("r", 3)
  }
}
.xy_chart {
  position: relative;
  left: 70px;
  top: 100px;
}
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script><svgclass="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.

Post a Comment for "How To Plot Data Points On Multi-line Chart With Multiple Y Axes"