summaryrefslogtreecommitdiff
path: root/horizon/static/horizon/js/horizon.d3circleschart.js
blob: 305b218203edb734815675924788c07b826c05e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
    Draw circles chart in d3.

    To use, a div is required with the data attributes
    data-chart-type="circles_chart", data-url and data-size in the
    div.

    data-chart-type - must be "circles_chart" so chart gets initialized
    data-url        - (string) url for the json data for the chart
    data-time       - (string) time parameter, gets appended to url as time=...
    data-size       - (integer) size of the circles in pixels

    If used in popup, initialization must be made manually e.g.:
      addHorizonLoadEvent(function() {
          horizon.d3_circles_chart.init('.html_element');
          horizon.d3_circles_chart.init('.health_chart');
      });


    Example:
      <div class="html_element"
           data-chart-type="circles_chart"
           data-url="/infrastructure/racks/1/top_communicating.json?cond=to"
           data-time="now"
           data-size="22">
      </div>

    There are controll elements for the cirles chart, implementing some commands
    that will be executed over the chart.

    1. The selectbox for time change, implements ChangeTime command. It has to
    have data attribute data-circles-chart-command="change_time", with defined
    receiver as jquery selector (selector can point to more elements and it will
    execute the command on all of them) e.g. data-receiver="#rack_health_chart"
    Option value is then appended to url and chart is refreshed.

    Example
    <div class="span3 circles_chart_time_picker">
      <select data-circles-chart-command="change_time"
              data-receiver="#rack_health_chart">
        <option value="now">Now</option>
        <option value="yesterday">Yesterday</option>
        <option value="last_week">Last Week</option>
        <option value="last_month">Last Month</option>
      </select>
    </div>
    <div class="clear"></div>

    2. Bootstrap tabs for switching different circle_charts implement command
    ChangeUrl. It has to have data attribute data-circles-chart-command="change_url",
    with defined receiver as jquery selector (selector can point to more elements and
    it will execute the command on all of them) e.g. data-receiver="#rack_health_chart"

    Inner li a element has to have  attribute data-url, e.g.
    data-url="/infrastructure/racks/1/top_communicating.json?type=alerts"

    The url of the chart is then switched and chart is refreshed.

    <ul class="nav nav-tabs"
        data-circles-chart-command="change_url"
        data-receiver="#rack_health_chart">
      <li class="active">
        <a data-url="/infrastructure/racks/1/top_communicating.json?type=overall_health" href="#">
          Overall Health</a>
      </li>
      <li>
        <a data-url="/infrastructure/racks/1/top_communicating.json?type=alerts" href="#">
          Alerts</a>
      </li>
      <li>
        <a data-url="/infrastructure/racks/1/top_communicating.json?type=capacities" href="#">
          Capacities</a>
      </li>
      <li>
        <a data-url="/infrastructure/racks/1/top_communicating.json?type=status" href="#">
          Status</a>
      </li>
    </ul>
*/


horizon.d3_circles_chart = {
  CirclesChart: function(chart_class, html_element){
    this.chart_class = chart_class;
    this.html_element = html_element;

    var jquery_element = $(html_element);
    this.size = jquery_element.data('size');
    this.time = jquery_element.data('time');
    this.url = jquery_element.data('url');

    this.final_url = this.url;
    if (this.final_url.indexOf('?') > -1){
      this.final_url += '&time=' + this.time;
    }else{
      this.final_url += '?time=' + this.time;
    }

    this.time = jquery_element.data('time');
    this.data = []

    this.refresh = refresh;
    function refresh(){
      var self = this;
      this.jqxhr = $.getJSON( this.final_url, function() {
            //FIXME add loader in the target element
          })
          .done(function(data) {
            // FIXME find a way how to only update graph with new data
            // not delete and create
            $(self.html_element).html("");

            self.data = data.data;
            self.settings = data.settings;
            self.chart_class.render(self.html_element, self.size, self.data, self.settings);
          })
          .fail(function() {
            // FIXME add proper fail message
            console.log( "error" ); })
          .always(function() {
            // FIXME add behaviour that should be always done
          });
    }
  },
  init: function(selector, settings) {
    var self = this;
    $(selector).each(function() {
      self.refresh(this);
    });
    self.bind_commands();
  },
  refresh: function(html_element){
    var chart = new this.CirclesChart(this, html_element)
    // FIXME save chart objects somewhere so I can use them again when
    // e.g. I am swithing tabs, or if I want to update them
    // via web sockets
    // this.charts.add_or_update(chart)
    chart.refresh();
  },
  render: function(html_element, size, data, settings){
    var self = this;
    // FIXME rewrite to scatter plot once we have some cool D3 chart
    // library
    var width = size + 4,
        height = size + 4,
        round = size / 2;
        center_x = width / 2,
        center_y = height / 2;

    var svg = d3.select(html_element).selectAll("svg")
      .data(data)
      .enter().append("svg")
      .attr("width", width)
      .attr("height", height);

    // FIXME use some pretty tooltip from some library we will use
    // this one is just temporary
    var tooltip = d3.select(html_element).append("div")
      .style("position", "absolute")
      .style("z-index", "10")
      .style("visibility", "hidden")
      .style("min-width", "100px")
      .style("max-width", "110px")
      .style("min-height", "30px")
      .style("border", "1px ridge grey")
      .style("background-color", "white")
      .text(function(d) { "a simple tooltip"; });

    var circle = svg.append("circle")
        .attr("r", round)//function(d) { return d.r; })// can be sent form server
        .attr("cx", center_x)
        .attr("cy", center_y)
        .attr("stroke", "#cecece")
        .attr("stroke-width", function(d) {
          return 1;
        })
        .style("fill", function(d) {
          if (d.color){
            return d.color;
          } else if (settings.scale == "linear_color_scale"){
            return self.linear_color_scale(d.percentage, settings.domain, settings.range)

          }
        })
        .on("mouseover", function(d){
          if (d.tooltip) {
            tooltip.html(d.tooltip);
          } else {
            tooltip.html(d.name + "<br/>" + d.status);
          }
          tooltip.style("visibility", "visible");})
        .on("mousemove", function(d){tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");})
        .on("mouseout", function(d){tooltip.style("visibility", "hidden");});
        ;

    /*
    // or just d3 title element
    circle.append("svg:title")
      .text(function(d) { return d.x; });

    */
  },
  linear_color_scale: function(percentage, domain, range){
    usage_color = d3.scale.linear()
      .domain(domain)
      .range(range);
    return usage_color(percentage);
  },
  bind_commands: function (){
    var change_time_command_selector = 'select[data-circles-chart-command="change_time"]';
    var change_url_command_selector = '[data-circles-chart-command="change_url"]';
    var self = this;
    bind_change_time = function(){
      $(change_time_command_selector).each(function() {
        $(this).change(function(){
          var invoker = $(this);
          var command = new self.Command.ChangeTime(self, invoker);
          command.execute();
        });
      });
    }
    bind_change_url = function(){
      $(change_url_command_selector + ' a').click(function (e) {
        // Bootstrap tabs functionality
        e.preventDefault();
        $(this).tab('show');

        // Command for url change and refresh
        var invoker = $(this);
        var command = new self.Command.ChangeUrl(self, invoker);
        command.execute();
      })
    }
    bind_change_time();
    bind_change_url();
  },
  Command: {
    ChangeTime: function (chart_class, invoker){
      // Invoker of the command should know about it's receiver.
      // Also invoker brings all parameters of the command.
      this.receiver_selector = invoker.data('receiver');
      this.new_time = invoker.find("option:selected").val();

      this.execute = execute;
      function execute(){
          var self = this;
          $(this.receiver_selector).each(function(){
            // change time of the chart
            $(this).data('time', self.new_time);
            // refresh the chart
            chart_class.refresh(this)
          });
      }
    },
    ChangeUrl: function (chart_class, invoker, new_url){
      // Invoker of the command should know about it's receiver.
      // Also invoker brings all parameters of the command.
      this.receiver_selector = invoker.parents('ul').first().data('receiver');
      this.new_url = invoker.data('url');

      this.execute = execute;
      function execute(){
        var self = this;
        $(this.receiver_selector).each(function(){
          // change time of the chart
          $(this).data('url', self.new_url);
          // refresh the chart
          chart_class.refresh(this)
        });
      }
    }
  }
}

/* init the graphs */
horizon.addInitFunction(function () {
    horizon.d3_circles_chart.init('div[data-chart-type="circles_chart"]');
});