d3.js: sélection synchrone sur plusieurs graphiques

Nov 09 2020

Comment dois-je gérer la sélection lorsque je souhaite archiver une sélection synchrone sur plusieurs graphiques? Lorsque je fais une sélection, la même sélection doit être affichée sur d'autres graphiques également.

Je vous ai ajouté un exemple jsfiddle qui a été réalisé dans dc.js.

Mon code pour le moment ressemble à:

var signalData = {
      signal1: {
        name: "signal1",
        data: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 1, 2, 3, 5, 4, 1, 4, 2, 9, 7, 5, 7, 4, 6],
      },
      signal2: {
        name: "signal2",
        data: [6, 4, 8, 5, 4, 8, 4, 3, 5, 4, 5, 8, 7, 2, 9, 5, 4, 1, 2, 6, 0, 5, 7, 1],
      },
      signal3: {
        name: "signal3",
        data: [9, 5, 12, 3, 8, 4, 8, 6, 3, 4, 7, 8, 5, 2, 1, 8, 6, 8, 5, 8, 4, 8, 5, 1],
      },
    }

    var margin = {
      top: 10,
      right: 50,
      bottom: 40,
      left: 50
    };
    var width = window.innerWidth - margin.left - margin.right;
    var height = 200 - margin.top - margin.bottom;

    var xScale = d3
      .scaleLinear()
      .domain([0, d3.max(Object.keys(signalData), d => signalData[d].data.length)]) // input
      .range([0, width]); // output

    var brushXScale = d3
      .scaleLinear()
      .domain([0, d3.max(Object.keys(signalData), d => signalData[d].data.length)]) // input
      .range([0, width]); // output

    var brush = d3.brushX().extent([
      [0, 0],
      [width - margin.right, height],
    ]).on("brush", brushed);

    function brushed(d) {
      var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

      // transform from pixels to x-values
      var selectionX = [xScale.invert(selectionPx[0]), xScale.invert(selectionPx[1])];

      // set x scale domain, then redraw the lines
      brushXScale.domain(selectionX);

      for (var signal in signalData) {
        updateChart(
          signalData[signal].data,
          signalData[signal].name
        );
      }
    }

    //Generate the brush focus chart
    generateMinimap(signalData.signal1.data);
    //Generate charts dynamically as often as i have signals
    for (var signal in this.signalData) {
      generateChart(
        signalData[signal].data,
        signalData[signal].name
      );
    }

    // This function is for the one time preparations
    function generateChart(data, name) {
      var svg = d3
        .select("#" + name)
        .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 + ")");

      svg
        .append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")

      svg.append("g").attr("class", "y axis");

      svg
        .append("path")
        .attr("class", "line") // Assign a class for styling
        .attr("fill", "none")
        .attr("stroke", "blue");

      updateChart(data, name);
    }

    // This function needs to be called to update the already prepared chart
    function updateChart(data, name) {
      var svg = d3.select("#" + name + " svg");

      var yScale = d3
        .scaleLinear()
        .domain([0, d3.max(data)]) // input
        .range([height, 0]); // output

      var line = d3
        .line()
        .x((d, i) => brushXScale(i))
        .y((d) => yScale(d));
      console.log(line([1, 2, 3, 4]));

      svg.select(".x.axis")
        .call(d3.axisBottom(brushXScale));

      svg.select(".y.axis")
        .call(d3.axisLeft(yScale));

      svg
        .select(".line")
        .datum(data) // 10. Binds data to the line
        .attr("d", line) // 11. Calls the line generator
    }

    function generateMinimap(data) {
      var yScale = d3
        .scaleLinear()
        .domain([0, d3.max(data)]) // input
        .range([height, 0]); // output

      var line = d3
        .line()
        .x((d, i) => xScale(i))
        .y((d) => yScale(d));

      var svg = d3
        .select("#minimap")
        .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 + ")");

      svg.append("g").call(brush);

      svg
        .append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom

      svg
        .append("path")
        .datum(data) // 10. Binds data to the line
        .attr("class", "line") // Assign a class for styling
        .attr("d", line) // 11. Calls the line generator
        .attr("fill-opacity", "0.17")
        .attr("fill", "blue")
        .attr("stroke", "blue");
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
  <div id="minimap"></div>
  <!-- In the original project these divs are not 
    static and get generated with v-for as many times as 
    i have a signal in signalData -->
  <div id="signal1"></div>
  <div id="signal2"></div>
  <div id="signal3"></div>

Il est important de savoir que les graphiques sont rendus dynamiquement et que le nombre de graphiques peut être plus ou moins élevé!

J'ai essayé d'extraire les données de la sélection et de les ajouter aux autres mais je ne suis pas parvenu à un résultat.

Réponses

1 RubenHelsloot Nov 11 2020 at 03:58

Basé sur cet exemple , vous pouvez appeler par brushprogramme .

De cette façon, chaque instance reçoit la même mise en évidence. J'ai créé un nouveau pinceau selectBrushqui appelle tous les autres conteneurs de pinceaux et y applique le même pinceau.

var signalData = {
  signal1: {
    name: "signal1",
    data: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 1, 2, 3, 5, 4, 1, 4, 2, 9, 7, 5, 7, 4, 6],
  },
  signal2: {
    name: "signal2",
    data: [6, 4, 8, 5, 4, 8, 4, 3, 5, 4, 5, 8, 7, 2, 9, 5, 4, 1, 2, 6, 0, 5, 7, 1],
  },
  signal3: {
    name: "signal3",
    data: [9, 5, 12, 3, 8, 4, 8, 6, 3, 4, 7, 8, 5, 2, 1, 8, 6, 8, 5, 8, 4, 8, 5, 1],
  },
};

var margin = {
  top: 10,
  right: 50,
  bottom: 40,
  left: 50,
};
var width = window.innerWidth - margin.left - margin.right;
var height = 200 - margin.top - margin.bottom;

var xScale = d3
  .scaleLinear()
  .domain([0, d3.max(Object.keys(signalData), d => signalData[d].data.length)]) // input
  .range([0, width]); // output

var brushXScale = d3
  .scaleLinear()
  .domain([0, d3.max(Object.keys(signalData), d => signalData[d].data.length)]) // input
  .range([0, width]); // output

var zoomBrush = d3.brushX()
  .extent([
    [0, 0],
    [width - margin.right, height],
  ])
  .on("brush", zoomBrushed);

var selectBrush = d3.brushX()
  .extent([
    [0, 0],
    [width - margin.right, height],
  ])
  .on("brush", selectBrushed);

function zoomBrushed() {
  var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

  // transform from pixels to x-values
  var selectionX = [xScale.invert(selectionPx[0]), xScale.invert(selectionPx[1])];

  // set x scale domain, then redraw the lines
  brushXScale.domain(selectionX);

  for(var signal in signalData) {
    updateChart(
      signalData[signal].data,
      signalData[signal].name,
    );
  }
}

function selectBrushed() {
  var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels

  selectBrush.on("brush", null);
  // This is the starts-with selector (https://www.w3schools.com/cssref/sel_attr_begin.asp)
  d3.selectAll("div[id^=signal] svg .brushcontainer")
    .call(selectBrush.move, selectionPx);
  selectBrush.on("brush", selectBrushed);
}

//Generate the brush focus chart
generateMinimap(signalData.signal1.data);
//Generate charts dynamically as often as i have signals
for(var signal in this.signalData) {
  generateChart(
    signalData[signal].data,
    signalData[signal].name,
  );
}

// This function is for the one time preparations
function generateChart(data, name) {
  var svg = d3
    .select("#" + name)
    .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 + ")");

  svg
    .append("g")
    .attr("class", "brushcontainer")
    .call(selectBrush);
  
  svg
    .append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")");

  svg.append("g")
    .attr("class", "y axis");

  svg
    .append("path")
    .attr("class", "line") // Assign a class for styling
    .attr("fill", "none")
    .attr("stroke", "blue");

  updateChart(data, name);
}

// This function needs to be called to update the already prepared chart
function updateChart(data, name) {
  var svg = d3.select("#" + name + " svg");

  var yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // input
    .range([height, 0]); // output

  var line = d3
    .line()
    .x((d, i) => brushXScale(i))
    .y((d) => yScale(d));

  svg.select(".x.axis")
    .call(d3.axisBottom(brushXScale));

  svg.select(".y.axis")
    .call(d3.axisLeft(yScale));

  svg
    .select(".line")
    .datum(data) // 10. Binds data to the line
    .attr("d", line); // 11. Calls the line generator
}

function generateMinimap(data) {
  var yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data)]) // input
    .range([height, 0]); // output

  var line = d3
    .line()
    .x((d, i) => xScale(i))
    .y((d) => yScale(d));

  var svg = d3
    .select("#minimap")
    .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 + ")");

  svg.append("g")
    .call(zoomBrush);

  svg
    .append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom

  svg
    .append("path")
    .datum(data) // 10. Binds data to the line
    .attr("class", "line") // Assign a class for styling
    .attr("d", line) // 11. Calls the line generator
    .attr("fill-opacity", "0.17")
    .attr("fill", "blue")
    .attr("stroke", "blue");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="minimap"></div>
<!-- In the original project these divs are not 
    static and get generated with v-for as many times as 
    i have a signal in signalData -->
<div id="signal1"></div>
<div id="signal2"></div>
<div id="signal3"></div>