Jamie Hyland
Jamie Hyland

Reputation: 116

Creating a real time changing rectangle in d3.js with a bitcoin WebSocket

I have access to a bitcoin websocket which is giving me the current market price for the bitcoin every minute. I want to make a single rectangle that changes dynamically to these values being currently passed into my bardata array.

Here is the code:

<html>
<script type="text/javascript" src="js/d3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>


<body>

    <script>
        var webSocket = new WebSocket('wss://socket.cbix.ca/index'); //creating a websocket connection to blockhain.info
        webSocket.onopen = function (event) {
            console.log('Websocket connection open');
        };

        var bardata = [];

        webSocket.onmessage = function (event) {
            var e = jQuery.parseJSON(event.data);
            console.log(e);
            var high = e.high;
            console.log(high);
            update();
            bardata.push(high);
            $('.message').prepend("<p>" + "The highest bitcoin value for this minute is: $" + high + "</p>");

        }



        var height = 730
            , width = 1080
            , barWidth = 50
            , barOffset = 5;

        var yScale = d3.scale.linear()
            .domain([0, d3.max(bardata + 100)])
            .range([0, height])

        var svg = d3.select('body').append('svg')
            .attr('width', width)
            .attr('height', height)
            .style('background', '#C9D7D6')

        function update() {
            svg.selectAll('rect').data(bardata)
                .enter().append('rect')
                .style('fill', '#C61C6F')
                .attr('width', barWidth)
                .attr('height', function (d) {
                    return yScale(d);
                })
                .attr('x', function (d, i) {
                    return i * (barWidth + barOffset);
                })
                .attr('y', function (d) {
                    return height - yScale(d);
                }).transition()
                .attr('height', function (d) {
                    return yScale(d);
                })
                .attr('y', function (d) {
                    return height - yScale(d);
                })
                .delay(function (d, i) {
                    return i * 20;
                })
                .duration(1000)
                .ease('elastic')
        }
    </script>
    <div class="message">
    </div>


</body>

</html>

However when I run this code a new bar is created every time. I know this is due to the update function re drawing everything once the .onmessage function updates again. I was wondering if there was another way to go about this maybe using AJAX or something? Some help would be greatly appreciated.

Upvotes: 0

Views: 345

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

The problem you're facing is that your update function has only an "enter" selection. In this situation, the expected behaviour is having only 1 non-changing bar (because the "enter" selection is empty from the second time on). But as you're pushing values to barData, the length of this barData array is increasing, and the bars you're seeing are the new "enter" selections.

So, the first change is using just the last value of barData, not the whole array.

The second change is writing a proper enter and update selections. This is how you should do. First, bind the data:

var rect = svg.selectAll("rect")
    .data([data]);

Then, create your "enter" selection:

rect.enter()
    .append("rect").attr("x", 0)
    .attr("y", 25)
    .attr("height", 50)
    .attr("width", d => scale(d))
    .attr("fill", "#C61C6F");

This "enter" selection will work only the first time you call update. From the second time on, you'll only change the existing bar:

rect.transition()
    .duration(200)
    .ease('elastic')
    .attr("width", d => scale(d));

Here is a demo:

setInterval(() => {
    var data = ~~(Math.random() * 100)+1;
    update(data);
}, 2000);



var svg = d3.select("#svg")
    .append("svg")
    .attr("width", 300)
    .attr("height", 100);

var text = d3.select("#text");

var scale = d3.scale.linear()
    .range([0, 300])
    .domain([0, 100]);

update(50);

function update(data) {

    var rect = svg.selectAll("rect")
        .data([data]);

    rect.enter()
        .append("rect").attr("x", 0)
        .attr("y", 25)
        .attr("height", 50)
        .attr("width", d => scale(d))
        .attr("fill", "#C61C6F");

    rect.transition().duration(500).ease('elastic').attr("width", d => scale(d));

    text.text("The highest bitcoin value for this minute is: $" + data)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="text"></div>
<div id="svg"></div>

Upvotes: 1

Related Questions