
// Function to create a reusable line chart
function lineChart(selector, data, params) {

    var config = {
        margin: { top: 10, right: 10, bottom: 40, left: 50 },
        xlabel: "x",
        ylabel: "y",
        xrange: null,
        yrange: null,
    }

    config = { ...config, ...params };
    
    const container = d3.select(selector)
    
    // Get the dimensions of the container
    const containerWidth = 400
    const containerHeight = 250
    
    // Set up dimensions based on the container size and margins
    const margin = config.margin;
    const width = containerWidth - margin.left - margin.right;
    const height = containerHeight - margin.top - margin.bottom;
    
    // Create the SVG container with viewBox
    const svg = container.append("svg")
          .attr("width", "100%")
          .attr("height", "100%")
          .attr("viewBox", `0 0 ${containerWidth} ${containerHeight}`)
          .attr("preserveAspectRatio", "xMidYMid meet")
          .append("g")
          .attr("transform", `translate(${margin.left},${margin.top})`);

    // Set up scales
    const xScale = d3.scaleLinear()
          .domain(config.xrange || d3.extent(data, d => d.x))
          .range([0, width]);

    const yScale = d3.scaleLinear()
          .domain(config.yrange || d3.extent(data, d => d.y))
          .range([height, 0]);

    // Create line generator
    const line = d3.line()
          .x(d => xScale(d.x))
          .y(d => yScale(d.y));

    // Draw line
    svg.append("path")
        .attr("class", "line")
        .style("stroke", "steelblue")
        .style("fill", "transparent")
        .attr("d", line(data));

    // Draw x-axis
    svg.append("g")
        .attr("class", "x-axis")
        .attr("transform", `translate(0, ${height})`)
        .call(d3.axisBottom(xScale))
        .append("text")
        .attr("x", width / 2)
        .attr("y", margin.bottom - 10)
        .attr("fill", "black")
        .text(config.xlabel);

    // Draw y-axis
    svg.append("g")
        .attr("class", "y-axis")
        .call(d3.axisLeft(yScale))
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", -margin.left)
        .attr("x", -height / 2)
        .attr("dy", "1em")
        .attr("fill", "black")
        .text(config.ylabel);

    // Function to update the chart with new data
    function update(newData) {
        // Update scales with new data
        xScale.domain(config.xrange || d3.extent(newData, d => d.x));
        yScale.domain(config.yrange || d3.extent(newData, d => d.y));

        // Update line, showing only the last 10 points
        svg.select(".line")
            .transition()
            .attr("d", line(newData));

        // Update x-axis
        svg.select(".x-axis")
            .transition()
            .call(d3.axisBottom(xScale));

        // Update y-axis
        svg.select(".y-axis")
            .transition()
            .call(d3.axisLeft(yScale));
    }

    // Return the update function for later use
    return { update };
}

///////////////////////////////////////////////////////////////////////////////////////

// Function to create a reusable heatmap
function matrixChart(selector, data,
                         config={
                             margin:null,
                             xlabel:"x",
                             ylabel:"y",
                         },
                    ) {
    
  const container = d3.select(selector);

  // Get the dimensions of the container
  const containerWidth = 400;
  const containerHeight = 250;

  // Set up dimensions based on the container size and margins
  const margin = config.margin || { top: 10, right: 10, bottom: 40, left: 50 };
  const width = containerWidth - margin.left - margin.right;
  const height = containerHeight - margin.top - margin.bottom;

  // Get the dimensions of the data array
  const n = data.length;
  const m = data[0].length;

  // Ensure equal scaling for both axes to create square cells
  const cellSize = Math.min(width / m, height / n);

  // Create the SVG container with viewBox
  const svg = container
    .append("svg")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
    .attr("preserveAspectRatio", "xMidYMid meet")
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

    // Set up scales
    
    const xScale = d3.scaleLinear()
          .domain([1, m])
          .range([0, cellSize*m]);
    const yScale = d3.scaleLinear()
          .domain([1, n])
          .range([cellSize*n, 0]);

  // Set up color scale
    const colorScale = d3.scaleSequential(d3.interpolateCividis).domain([0, 1]);


    const valText = svg.append("text")
          .style("font-size","0.6em")
    
  // Draw heatmap rectangles
    const heatmapRects = svg.selectAll(".heatmap-rect")
    .data(data.flat()) // Flatten the 2D array into a 1D array
    .enter()
    .append("rect")
    .attr("data-value", d => d)
    .attr("class", "heatmap-rect")
    .attr("x", (_, i) => (i % m) * cellSize) // Compute x position from the flattened index
    .attr("y", (_, i) => Math.floor(i / m) * cellSize) // Compute y position from the flattened index
    .attr("width", cellSize)
    .attr("height", cellSize)
     .style("fill", d => colorScale(d))
        .on("mouseover", function(event, d) {
            valText.text(d.toFixed(2))
    })


    
  // Draw x-axis with ticks corresponding to cellSize
  svg.append("g")
    .attr("class", "x-axis")
    .attr("transform", `translate(0, ${cellSize*n})`)
    .call(d3.axisBottom(xScale))
   .append("text")
    .attr("x", cellSize*m / 2)
    .attr("y", margin.bottom - 10)
    .attr("fill", "black")
    .text(config.xlabel)
    
  // Draw y-axis with ticks corresponding to cellSize
  svg.append("g")
    .attr("class", "y-axis")
    .call(d3.axisLeft(yScale))
   .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", -margin.left)
    .attr("x", -cellSize*n / 2)
    .attr("dy", "1em")
    .attr("fill", "black")
    .text(config.ylabel);
    
  // Function to update the chart with new data
  function update(newData) {
    // Update heatmap rectangles
    heatmapRects.data(newData.flat())
      .transition()
      .style("fill", d => colorScale(d));

    // Update x-axis
    svg.select(".x-axis")
      .transition()
      .call(d3.axisBottom(xScale));

    // Update y-axis
    svg.select(".y-axis")
      .transition()
      .call(d3.axisLeft(yScale));
  }


    // Function to update the color scale dynamically
    function setColorScale(domain) {
        colorScale.domain(domain);
        // Update heatmap colors
        heatmapRects.transition().style("fill", d => colorScale(d));
    }
    
  // Return the update function and setColorScale function for later use
    return { update, setColorScale };
    
}

/////////////////////////////////////////////////////////////////////

// Function to create a reusable line chart
function linepointChart(selector, data,
                         config={
                             margin:null,
                             xlabel:"x",
                             ylabel:"y",
                             xrange : null,
                             yrange : null,
                         },
                       ) {
    
    const container = d3.select(selector);

    // Get the dimensions of the container
    const containerWidth = 400;
    const containerHeight = 250;

    // Set up dimensions based on the container size and margins
    const margin = config.margin || { top: 10, right: 10, bottom: 40, left: 50 };
    const width = containerWidth - margin.left - margin.right;
    const height = containerHeight - margin.top - margin.bottom;

    // Create the SVG container with viewBox
    const svg = container.append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .attr("viewBox", `0 0 ${containerWidth} ${containerHeight}`)
        .attr("preserveAspectRatio", "xMidYMid meet")
        .append("g")
        .attr("transform", `translate(${margin.left},${margin.top})`);

    // Set up scales
    const xScale = d3.scaleLinear()
        .domain(d3.extent(data, d => d.x))
        .range([0, width]);

    let yScale = d3.scaleLinear()
        .domain(d3.extent(data, d => d.y))
        .range([height, 0]);

    // Create line generator
    const line = d3.line()
        .x(d => xScale(d.x))
        .y(d => yScale(d.y));

    // Draw line
    const linePath = svg.append("path")
        .attr("class", "line")
        .style("stroke", "steelblue")
        .style("fill", "transparent")
        .attr("d", line(data));

    // Draw points
    const pointsGroup = svg.append("g").attr("class", "points");
    let points = pointsGroup.selectAll("circle")
        .data(data)
        .enter().append("circle")
        .attr("cx", d => xScale(d.x))
        .attr("cy", d => yScale(d.y))
        .attr("r", 3)
        .style("fill", "steelblue");
    
    // Draw x-axis
    svg.append("g")
        .attr("class", "x-axis")
        .attr("transform", `translate(0, ${height})`)
        .call(d3.axisBottom(xScale))
        .append("text")
        .attr("x", width / 2)
        .attr("y", margin.bottom - 10)
        .attr("fill", "black")
        .text(config.xlabel || "X Axis");

    // Draw y-axis
    svg.append("g")
        .attr("class", "y-axis")
        .call(d3.axisLeft(yScale))
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", -margin.left)
        .attr("x", -height / 2)
        .attr("dy", "1em")
        .attr("fill", "black")
        .text(config.ylabel || "Y Axis");


function update(newData) {
    // Update scales with new data
    xScale.domain(d3.extent(newData, d => d.x));
    yScale.domain(d3.extent(newData, d => d.y));

        // Update x-axis
        svg.select(".x-axis")
            .transition()
            .call(d3.axisBottom(xScale));

        // Update y-axis
        svg.select(".y-axis")
            .transition()
            .call(d3.axisLeft(yScale));

    // Update line
    linePath.transition().attr("d", line(newData));

    // Update points
    points = points.data(newData);

    // Remove old points
    points.exit().remove();

    // Update existing points and add new points using join
    points = points.join(
        enter => enter.append("circle")
            .attr("r", 3)
            .style("fill", "steelblue")
            .attr("cx", d => xScale(d.x))
            .attr("cy", d => yScale(d.y)),
        update => update.transition()
            .attr("cx", d => xScale(d.x))
            .attr("cy", d => yScale(d.y))
    );
}

    // Function to show/hide the line
    function showLine(show) {
        linePath.style("display", show ? "initial" : "none");
    }

    // Function to show/hide the points
    function showPoints(show) {
        pointsGroup.style("display", show ? "initial" : "none");
    }

    // Function to show/hide the points
    function ylog(show) {

        yScale = show ? d3.scaleLog() : d3.scaleLinear()
        yScale
            .domain(d3.extent(data, d => d.y))
            .range([height, 0]);

        update(data)
        
    }

    // Return the update function and control functions for later use
    return { update, showLine, showPoints, ylog };
}

