turnip
turnip

Reputation: 2346

Dynamically scale data in an interactive chart, in which the data gets altered?

I have made an interactive bar chart, where the bars can be dragged up and down to adjust the data.

As the bar is dragged beyond the current max/min of the y-axis domain, the y-axis scales accordingly. However, I cannot get the rest of the bars to scale accordingly (i.e: if I increase one bar's value to a new extreme, the other bars should shrink along with the new scale)

I have a JS Fiddle here with everything that works so far.

// canvas properties
var margin =
    {
        top: 40,
        bottom: 40,
        right: 30,
        left: 50
    }

var w = 960 - margin.left - margin.right,
    h = 500 - margin.top - margin.bottom;

// initiating axes
var x = d3.scale.ordinal()
        .rangeRoundBands([0, w], 0.1);

var y = d3.scale.linear()
        .range([h, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickSize(0)
    .tickPadding(6);

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var zeroline = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickSize(0)
    .tickFormat('')

var svg = d3.select("body").append("svg")
    .attr("width", w + margin.left + margin.right)
    .attr("height", h + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var newValue;    
var data = [
  {name: "A", value: -15},
  {name: "B", value: -20},
  {name: "C", value: -22},
  {name: "D", value: -18},
  {name: "E", value: 2},
  {name: "F", value: 6},
  {name: "G", value: 26},
  {name: "H", value: 18}
];

function type(d)
{
    d.value = +d.value;
    return d;
}

function generateChart(error, data)
{
    /* ========== Parse Data & Create Axes ========== */
     // create a new property called y (needed for d3.events)
    var data = data.map(function (d, i)
    {
        return {
            name: d.name,
            value: d.value,
            y: d.value
        }
    });

    var max = d3.max(data, function (d) { return d.y; });
    var min = -max;

    y.domain([min, max]).nice();
    x.domain(data.map(function (d) { return d.name; }));

    var zz = svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + (h / 2) + ")")
    .call(zeroline);

    var xx = svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + (h + 20) + ")")
        .call(xAxis);

    var yy = svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    /* ========== Drag Behaviour for Rectangles ========== */
    var drag = d3.behavior.drag()
        .on("drag", resize);

    /* ========== Create Rectangles ========== */
    var DataBar = svg.selectAll(".bar")
        .data(data)
        .enter().append("rect")
        .attr("class", function (d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
        .attr("id", function (d) { return (d.value < 0 ? "negative" : "positive"); })
        .attr("x", function (d) { return x(d.name); })
        .attr("y", function (d) { return y(Math.max(0, d.value)); })
        .attr("width", x.rangeBand())
        .attr("height", function (d) { return Math.abs(y(d.value) - y(0)); })
        .attr("cursor", "ns-resize")
        .call(drag);

    /* ========== Drag Functions ========== */
    function resize(d)
    {
        if (d3.select(this)[0][0].id == 'positive')
        {

            d.y = d3.event.y;
            if (y.invert(d.y) >= 0) // positive -> postive
            {
                var barHeight = -(d.y - y(0));
                var bar = d3.select(this);
                bar.attr("y", function (d) { return d.y; })
                   .attr("height", barHeight)
                   .style("fill", "steelblue");

            }
            else if (y.invert(d.y) < 0) // positive -> negative
            {
                var barHeight = Math.abs((d.y) - y(0))
                var dragy = d3.event.y
                barHeight += dragy - (d.y);
                var bar = d3.select(this)
                bar.attr("height", barHeight)
                   .attr("y", y(0))
                   .style("fill", "darkorange");

            }

            newValue = y.invert(d.y);
        }
        else if (d3.select(this)[0][0].id == 'negative')
        {
            var barHeight = Math.abs(y(d.y) - y(0))
            var dragy = d3.event.y

            if (y.invert(dragy) < 0) // negative -> negative
            {
                barHeight += dragy - y(d.y);
                var bar = d3.select(this)
                bar.attr("height", barHeight)
                   .attr("y", y(0))
                   .style("fill", "darkorange");
            }
            else if (y.invert(dragy) >= 0) // negative -> positive
            {

                var barHeight = -(dragy - y(0));

                var bar = d3.select(this);
                bar.attr("y", function (d) { return dragy; })
                   .attr("height", barHeight)
                   .style("fill", "steelblue");

            }

            //newValue = y.invert(dragy);

        }
        var max = d3.max(data, function (d) { return d.value; });
        var min = -max;
        var update = [];

        if (newValue > max)// || newValue < min)
        {
            y.domain([-newValue, newValue]).nice();
            yy.call(yAxis)
        }

    }    
}

generateChart('error!', data)

(Quick note: the y-axis rescaling only works with the initial blue bars at the moment.)

Upvotes: 0

Views: 71

Answers (1)

JSBob
JSBob

Reputation: 1046

Add the following block of code after the if (newValue > max) { ... } block:

var selectedObjectName = d3.select(this).data()[0].name;
svg.selectAll("rect.bar").filter(function (d){
   return (d.name != selectedObjectName);})
   .attr("height", function (d) { return Math.abs(y(d.value) - y(0));})
   .attr("y", function (d) { return y(Math.max(0, d.value)); });

The idea is to select all the rectangles, filter out the currently selected one, and re-adjust the height and y coordinate of the remaining rectangles. Fiddle

Upvotes: 1

Related Questions