vasturiano / timelines-chart

Timelines Chart
http://vasturiano.github.io/timelines-chart/example/categorical/
MIT License
551 stars 121 forks source link

onScaleUnitClick #134

Closed IRISHMAN04 closed 1 year ago

IRISHMAN04 commented 1 year ago

Is there a way to assign an onScaleUnitClick?

I'm trying to filter the data in the chart to only the scale unit a user clicks.

I've tried manually iterating over the rendered svg rects, and using thisRect.setAttribute('onclick',filter('${timelinesChart}','${thisText.innerHTML}'));

and then I was going to write my own filter to remove data by getting timelinesChart.data(), then setting timelinesChart.data(newData) and timelinesChart.refresh(). When I do that it converts timelinesChart to the string representation of a function (function o(n){return u(n,t),l(),o}).

I've also tried passing the query selector through and selecting the html element with the class "timelines-chart" but I don't know if it's possible to convert that from a html element back into a TimelinesChart object, because the result from document.getElementById(timelineDivSelector) is an object that doesn't have a data function.

vasturiano commented 1 year ago

@IRISHMAN04 thanks for reaching out.

You should maintain a master data set from which you filter the subsets, because if you filter from the timelinesChart.data() getter you can't go back after toggling filters.

But if you do, and simply invoke .data(newData) after each filter interaction (no need for .refresh()) the component should function correctly. Please double-check that you are feeding the correct data object to the chart after each filter operation though.

Also you might want to set your own filtering interaction outside of the graph's own legend. The current legend is a simple item that wasn't designed to be interactive like this. You can hide it if you do .topMargin(0), and then replace it with your own above the chart.

IRISHMAN04 commented 1 year ago

Thanks for the help, I'll make a seperate toggle box for it because I can't exactly do multi unit filtering with this. I did however figure out a way to get the onclicks to work so I've got that code here for anyone who wants it

/**
 * Setup the legend clicks.  
 * 
 * This has to run delayed after data enters the chart so that it happens after the legend has been created
 * 
 * @param {TimelinesChart} timelinesChart 
 * @param {HTMLElement} timelineDiv 
 * 
 */
function setupClicks(timelinesChart, timelineDiv) {
    const allColourSlots = timelineDiv.getElementsByClassName("color-slot");

    for (const slot of allColourSlots) {
        const rect = slot.getElementsByTagName("rect")[0]
        const text = slot.getElementsByTagName("text")[0]
        const myLambda = function () { filter_scale_unit(timelinesChart, text.innerHTML) };
        rect.addEventListener("click", myLambda);
        text.addEventListener("click", myLambda);
    }
}

/**
 * Filter the data based on the scale unit
 * 
 * @param {TimelinesChart} timelinesChart 
 * @param {string} scaleUnit 
 * 
 */
function filter_scale_unit(chart, scaleUnit) {
    const oldData = chart.data();
    const newData = JSON.parse(JSON.stringify(oldData));
    for (let i = newData.length - 1; i >= 0; i--) {
        let groupLength = 0
        for (const section of newData[i]["data"]) {
            for (let j = section["data"].length - 1; j >= 0; j--)
                if (section["data"][j]["val"] != scaleUnit)
                    section["data"].splice(j, 1);
            groupLength += section["data"].length
        }
        if (groupLength == 0)
            newData.splice(i, 1)
    }
    chart.data(newData);
}

The linchpin for me getting this to work correctly was making text a const, or else it would pass all my click handlers the text of the last item in my legend