Chartjs-Chart.js custom categorical x-axis filter

0👍

I answered this by storing the individual data sets outside of the config, and switching what the config is pointing at based on the button clicks.

I used d3s rollup function to group my data, then leveraged the fade class to see what datasets should and should not be seen!

original_data = [
      {x: "A", y: 65, mygroup: "Group 1"},
      {x: "B", y: 59, mygroup: "Group 1"},
      {x: "C", y: 20, mygroup: 'Group 1'},
      {x: "D", y: 12, mygroup: 'Group 2'},
      {x: "E", y: 11, mygroup: 'Group 2'},
      {x: "F", y: 10, mygroup: 'Group 2'},
      {x: "G", y: 12, mygroup: 'Group 3'},
      {x: "H", y: 11, mygroup: 'Group 3'},
      {x: "I", y: 10, mygroup: 'Group 3'}
]

original_data2 = [
      {x: "A", y: 65},
      {x: "B", y: 59,},
      {x: "C", y: 20,},
      {x: "D", y: 12,},
      {x: "E", y: 11},
      {x: "F", y: 10}
]

config = {colors: ['red', 'blue', 'green']}

let chartified = d3.rollups(
        original_data,
        (group) => {
            return {
                label: group[0].mygroup,
                data: group,
            };
        },
        (d) => d.mygroup
    ).map((group, i) => {
        const dataset = group[1];
        dataset.backgroundColor = config.colors[i];
        return dataset;
    });
console.log(chartified)
// 1. break out your data by dataset

/*
let data2 = [
  {
    label: "Dataset #2",
    backgroundColor: 'blue',
    data: [
      {x: "D", y: 12},
      {x: "E", y: 11},
      {x: "F", y: 10}
    ],
  }
]

let data1 = [{
    label: "Dataset #1",
    backgroundColor: 'red',
    data: [
      {x: "A", y: 65},
      {x: "B", y: 59},
      {x: "C", y: 20}
    ],
  }]

let all_data = [ ...data1, ...data2 ]

console.log(all_data)*
let named_data = [
  {name: "Dataset #1", data: data1},
  {name: "Dataset #2", data: data2}
]
*/

var option = {
  plugins: {
      legend: {
            display: false,
        }
  }
};


function updateLegend(click, output) {
    const element = click.target.parentNode;
    element.classList.toggle('fade');
    output.update();
}

function generateLegend(output, container) {
    if (document.querySelectorAll('.customLegend').length === 0) {
        const chartBox = document.querySelector(container);
        const div = document.createElement('DIV');
        div.setAttribute('class', 'customLegend');

        const ul = document.createElement('UL');

        output.legend.legendItems.forEach((dataset, index) => {
            const text = dataset.text;
            const stroke = dataset.strokeStyle;
            const fill = dataset.fillStyle;
            const fontColor = '#666';
            const dat = dataset.data;

            const li = document.createElement('LI');
            const spanBox = document.createElement('SPAN');
            spanBox.style.borderColor = stroke;

            if (fill == 'rgba(0,0,0,0.1)') {
                spanBox.setAttribute('class', 'legend-annotation');
            } else {
                spanBox.setAttribute('class', 'legend-content');
                spanBox.style.backgroundColor = fill;
            }

            const p = document.createElement('P');
            const textNode = document.createTextNode(text);

            li.onclick = (event) => {
              var target = event.target || event.srcElement;
              target.parentElement.classList.toggle('fade');
              
              // get all elements that are currently faded
              let to_omit = target.parentElement
                .parentElement
                .querySelectorAll(".fade")
              
              let omitted_data = [...to_omit].map(x => x.querySelector('p').innerHTML)
              
              if (to_omit.length === 0) {
                 output.data.datasets  = chartified
              } else if (to_omit.length === chartified.length) {
                output.data.datasets = []
              } else {
                output.data.datasets = chartified.filter(x => !omitted_data.includes(x.label))
              }
              
              output.update()
            };

            ul.appendChild(li);
            li.appendChild(spanBox);
            li.appendChild(p);
            p.appendChild(textNode);
        });

        chartBox.prepend(div);
        div.appendChild(ul);
    }
}

const customLegend = {
        id: 'customLegend',
        afterDraw(chart, args, options) {
            generateLegend(chart, '.chart-container');
        },
 };

new Chart('chart_0', {
    // this is the string the constructor was registered at, ie Chart.controllers.MyType
    type: 'bar',
    data: {datasets: chartified },
    options: option,
    plugins: [customLegend],
});

/*
data: [
  {},
  {},
  {},
]
*/
.chart-container {
  position: relative;
  max-width: 800px;
  margin: auto;
}

        .chartBox {
            width: 80%;
        }

        .customLegend ul {
            display: flex;
            flex-direction: row;
            margin: 0 auto;
            list-style: none;
            justify-content: center;
        }

        .customLegend ul li {
            margin: 15px;
            display: flex;
            align-items: center;
            cursor: pointer;
            flex-direction: row;
            line-height: 22px;
        }

        .customLegend p {
            font-family: 'Helvetica';
            font-size: 12px;
            color: #666;
        }

        .customLegend ul li.fade p {
            color: rgba(102, 102, 102, 0.5);
        }

        li.fade span {
            opacity: 0.3;
        }

        .customLegend ul li span {
            display: inline-block;
            margin-right: 15px;
        }

        .legend-content {
            height: 10px;
            width: 10px;
        }

        .legend-annotation {
            border-top-style: dotted;
            height: 0px;
            width: 20px;
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<div class="chart-container">
    <canvas id="chart_0"></canvas>
</div>

Leave a comment