Difference between revisions of "Widget:WFSPProject"

From Open Energy Information

m (Text replacement - "002F6C" to "001980")
(upgrades to viz)
Line 32: Line 32:
  
 
.area_context {
 
.area_context {
 +
  fill: none;
 +
  clip-path: url(#context_data_clip);
 +
}
 +
 +
.area_context_unselected {
 
   fill: none;
 
   fill: none;
 
}
 
}
  
 
.brush {
 
.brush {
 +
  clip-path: url(#brush_clip);
 +
}
 +
 +
.leftHandle {
 +
  clip-path: url(#brush_clip);
 +
}
 +
.rightHandle {
 
   clip-path: url(#brush_clip);
 
   clip-path: url(#brush_clip);
 
}
 
}
  
 
.zoom {
 
.zoom {
   cursor: move;
+
   cursor: ew-resize;
 
   fill: none;
 
   fill: none;
   pointer-events: all;
+
   pointer-events: auto;
 
}
 
}
  
Line 106: Line 118:
  
  
 +
 +
 +
 +
<!-- <link href="dist/css/datepicker.min.css" rel="stylesheet" type="text/css">
 +
<script src="dist/js/datepicker.min.js"></script>
 +
<script src="dist/js/i18n/datepicker.en.js"></script>
 +
 +
<div>
 +
  <input type="text"
 +
      data-range="true"
 +
      data-multiple-dates-separator=" - "
 +
      data-language="en"
 +
      class="datepicker"/>
 +
</div> -->
 +
 +
<script>
 +
      // $('#datepicker').datepicker({
 +
      //    minDate: current_date // Now can select only dates up until today
 +
      // })
 +
</script>
  
  
Line 125: Line 157:
 
             <label for="customEndDate">End Date</label>
 
             <label for="customEndDate">End Date</label>
 
             <input type="date" class="form-control" id="customEndDate" name="end-date">
 
             <input type="date" class="form-control" id="customEndDate" name="end-date">
          </div>
 
          <div class="form-button">
 
            <input type="button" class="btn btn-primary" id="view_custom_range" value="Submit" data-dismiss="modal">
 
 
           </div>
 
           </div>
 
         </form>
 
         </form>
 
       </div>
 
       </div>
       <div class="modal-footer"><div type="button" class="btn btn-default" data-dismiss="modal">Close</div></div>
+
       <div class="modal-footer">
 +
        <div class="form-button">
 +
          <input type="button" class="btn btn-primary" id="view_custom_range" value="Submit" data-dismiss="modal">
 +
          <div type="button" class="btn btn-default" data-dismiss="modal">Close</div>
 +
        </div>
 +
      </div>
 
     </div>
 
     </div>
 
   </div>
 
   </div>
Line 138: Line 172:
  
 
<!-- Preset Buttons -->
 
<!-- Preset Buttons -->
<div id="div_preset_btns" style="overflow: scroll">
+
<div id="div_preset_btns" style="overflow: scroll; display: none">
   <svg id="svg_preset_btns" width="585" height="50"></svg>
+
   <svg id="svg_preset_btns" width="700" height="50"></svg>
 
</div>
 
</div>
  
 
<!-- Graphs -->
 
<!-- Graphs -->
<div id="div_graphs">
+
<div id="div_graphs" style="display: none">
 
   <svg id="svg_graphs" width="750" height="1000"></svg>
 
   <svg id="svg_graphs" width="750" height="1000"></svg>
 
</div>
 
</div>
 
 
 
  
  
Line 188: Line 219:
 
       available_turbineIDs.push(String(object["turbine_id"]));
 
       available_turbineIDs.push(String(object["turbine_id"]));
 
     };
 
     };
     if (available_turbineIDs.includes(Turbine_ID)) { visualize(); };
+
     if (available_turbineIDs.includes(Turbine_ID)) {  
 +
      var x = document.getElementById('div_graphs');
 +
      x.style.display = 'block';
 +
      var x = document.getElementById('div_preset_btns');
 +
      x.style.display = 'block';
 +
      visualize();  
 +
    };
 
   };
 
   };
  
Line 197: Line 234:
 
         svg2 = d3.select("#svg_preset_btns"),
 
         svg2 = d3.select("#svg_preset_btns"),
 
         svg_width = parseInt( 0.9 * $(window).width() ),
 
         svg_width = parseInt( 0.9 * $(window).width() ),
         svg_height = parseInt(svg_width * (350 / 750) );
+
         svg_height = parseInt(svg_width * (425 / 750) );
  
 
     svg.attr("width", svg_width).attr("height", svg_height);
 
     svg.attr("width", svg_width).attr("height", svg_height);
 
     // svg2.attr("width", parseInt(svg_width));
 
     // svg2.attr("width", parseInt(svg_width));
  
     var margin_context = {top: parseInt(svg_height * 0.01), right: parseInt(svg_width * 0.01), bottom: parseInt(svg_height * 0.9), left: parseInt(svg_width * 0.05) },
+
     var margin_context = {top: parseInt(svg_height * 0.05), right: parseInt(svg_width * 0.01), bottom: parseInt(svg_height * 0.875), left: parseInt(svg_width * 0.05) },
 
         margin_focus1 = {top: parseInt(svg_height * 0.25), right: parseInt(svg_width * 0.46), bottom: parseInt(svg_height * 0.1), left: parseInt(svg_width * 0.11) },
 
         margin_focus1 = {top: parseInt(svg_height * 0.25), right: parseInt(svg_width * 0.46), bottom: parseInt(svg_height * 0.1), left: parseInt(svg_width * 0.11) },
 
         margin_focus2 = {top: parseInt(svg_height * 0.25), right: parseInt(svg_width * 0.01), bottom: parseInt(svg_height * 0.1), left: parseInt(svg_width * 0.56) },
 
         margin_focus2 = {top: parseInt(svg_height * 0.25), right: parseInt(svg_width * 0.01), bottom: parseInt(svg_height * 0.1), left: parseInt(svg_width * 0.56) },
Line 268: Line 305:
 
       var current_date = new Date();
 
       var current_date = new Date();
 
       var data_start = d3.min(data, function(d) { return d.dateTime; })
 
       var data_start = d3.min(data, function(d) { return d.dateTime; })
 +
      var timeslider_start = data_start;
 
       // xScale_focus1.domain(d3.extent(data, function(d) { return d.dateTime; }));
 
       // xScale_focus1.domain(d3.extent(data, function(d) { return d.dateTime; }));
 
       xScale_focus1.domain( [ data_start, current_date ] );
 
       xScale_focus1.domain( [ data_start, current_date ] );
Line 289: Line 327:
 
         .append('clipPath')
 
         .append('clipPath')
 
         .attr('id', 'brush_clip')
 
         .attr('id', 'brush_clip')
 +
        .append('rect')
 +
        .attr('x', 0)
 +
        .attr('y', 0)
 +
        .attr('width', width_context)
 +
        .attr('height', height_context);
 +
 +
      svg
 +
        .append('clipPath')
 +
        .attr('id', 'context_data_clip')
 +
        .attr('class', 'context_data_clip')
 
         .append('rect')
 
         .append('rect')
 
         .attr('x', 0)
 
         .attr('x', 0)
Line 351: Line 399:
 
           .attr("cy", function(d) { return yScale_focus(d.powerOutput); })
 
           .attr("cy", function(d) { return yScale_focus(d.powerOutput); })
 
           .attr("r", 4);
 
           .attr("r", 4);
 +
 +
      var dots = focus2.selectAll(".dot_selected").data(data);
 +
      dots.enter().append("circle")
 +
          .attr("class", "dot_selected")
 +
          .attr("cx", function(d) { return xScale_focus2(d.windSpeed);  })
 +
          .attr("cy", function(d) { return yScale_focus(d.powerOutput); })
 +
          .attr("r", 4)
 +
          .style("opacity", "0")
 +
          .style("fill", "#1976D2");
 +
 +
      var last_start = xScale_focus1.domain()[0]
 +
      var last_end = xScale_focus1.domain()[1]
 +
      var current_start = xScale_focus1.domain()[0]
 +
      var current_end = xScale_focus1.domain()[1]
  
 
       function update_scatter(d) {
 
       function update_scatter(d) {
         // console.log("Scatter");
+
         // draw scatter
         var dots = focus2.selectAll(".dot_selected").data(data.filter(function(d){ return ( ( d.dateTime > xScale_focus1.domain()[0] ) & ( d.dateTime < xScale_focus1.domain()[1] ) ); }));
+
        current_start = xScale_focus1.domain()[0]
        dots.enter().append("circle")
+
        current_end = xScale_focus1.domain()[1]
            .attr("class", "dot_selected")
+
        var s1 = 0, s2 = 0, e1 = 0, e2 = 0;
            .attr("cx", function(d) { return xScale_focus2(d.windSpeed)})
+
 
            .attr("cy", function(d) { return yScale_focus(d.powerOutput); })
+
        if (current_start > last_start) { s1 = last_start; s2 = current_start; start_mod = "remove";
            .attr("r", 4);
+
        } else { s2 = last_start; s1 = current_start; start_mod = "add"; };
        dots
+
        if (current_end > last_end) { e1 = last_end; e2 = current_end; end_mod = "add";
            .attr("cx", function(d) { return xScale_focus2(d.windSpeed);  })
+
         } else { e2 = last_end; e1 = current_end; end_mod = "remove"; };
            .attr("cy", function(d) { return yScale_focus(d.powerOutput); })
+
 
            .style("fill", "#1976D2");
+
        focus2.selectAll(".dot_selected")
        dots.exit().remove();
+
          .filter(function(d){ return (( d.dateTime > s1 ) & ( d.dateTime < s2 )) })
      };
+
          .style("opacity", function(){ if ( start_mod == "add" ) { return "0.5" } else { return "0" } });
 +
        focus2.selectAll(".dot_selected")
 +
          .filter(function(d){ return (( d.dateTime > e1 ) & ( d.dateTime < e2 )) })
 +
          .style("opacity", function(){ if ( end_mod == "add" ) { return "0.5" } else { return "0" } });
 +
        last_start = current_start;
 +
        last_end = current_end;
 +
      }
 +
 
 +
 
 +
      context.append("rect")
 +
          .style("fill", "steelblue")
 +
          .style("stroke", "#000")
 +
          .style("stroke-width", "2")
 +
          .style("opacity", "0.2")
 +
          .attr("width", width_context)
 +
          .attr("height", height_context);
 +
 
 +
      context.append("path") // append unselected context data as white lines
 +
          .datum(data)  
 +
          .attr("class", "area_context_unselected")
 +
          .attr("d", area_context)
 +
          .style("stroke", "#ffffff");
  
 
       context.append("path")
 
       context.append("path")
           .datum(data.filter(function(d){ return ( d.turbineID == Turbine_ID );} ))  
+
           .datum(data)  
 
           .attr("class", "area_context")
 
           .attr("class", "area_context")
 
           .attr("d", area_context)
 
           .attr("d", area_context)
 
           .style("stroke", "#1976D2");
 
           .style("stroke", "#1976D2");
 +
 +
      var leftHandle = context.append("rect")
 +
          .attr("class", "leftHandle")
 +
          .style("fill", "grey")
 +
          .style("stroke", "#000")
 +
          .style("stroke-width", "1")
 +
          .attr("x", "0")
 +
          .attr("y", "0")
 +
          .attr("width", "8")
 +
          .attr("height", height_context);
 +
 +
      var rightHandle = context.append("rect")
 +
          .attr("class", "rightHandle")
 +
          .style("fill", "grey")
 +
          .style("stroke", "#000")
 +
          .style("stroke-width", "1")
 +
          .attr("x", "0")
 +
          .attr("y", "0")
 +
          .attr("width", "8")
 +
          .attr("height", height_context);
  
 
       context.append("g")
 
       context.append("g")
Line 397: Line 500:
 
                           .attr("id","allButtons");
 
                           .attr("id","allButtons");
  
       var labels= ['Today','Last 7 Days','Last 30 Days','Custom Range','Reset'];
+
       var labels= ['Today','Last 7 Days','Last 30 Days','Last Year','Custom Range','Reset'];
  
 
       var defaultColor= "#303f9f";
 
       var defaultColor= "#303f9f";
Line 438: Line 541:
 
                                     .translate(-s[0], 0));
 
                                     .translate(-s[0], 0));
 
                                 } else if (i == 3) {
 
                                 } else if (i == 3) {
 +
                                    var t_svg = svg.transition()
 +
                                        .duration(1000);
 +
                                    var one_Year_Ago = d3.timeYear.offset(current_date, -1);
 +
                                    var s = [xScale_context(one_Year_Ago), xScale_context(current_date)];
 +
                                    t_svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
 +
                                    .scale(width_context / (s[1] - s[0]))
 +
                                    .translate(-s[0], 0));
 +
                                } else if (i == 4) {
 
                                       // var t_svg = svg.transition()
 
                                       // var t_svg = svg.transition()
 
                                       //    .duration(1000);
 
                                       //    .duration(1000);
Line 445: Line 556:
 
                                       // .scale(width_context / (s[1] - s[0]))
 
                                       // .scale(width_context / (s[1] - s[0]))
 
                                       // .translate(-s[0], 0));
 
                                       // .translate(-s[0], 0));
                                 } else if (i == 4) {
+
                                 } else if (i == 5) {
 +
                                    xScale_context.domain( [ data_start, current_date ] );
 +
                                    timeslider_start = data_start
 +
 
 +
                                    // REDRAW CONTEXT
 +
                                    context.selectAll("path").remove();
 +
                                    area_context.x(function(d) { return xScale_context(d.dateTime); });
 +
                                    context.append("path")  // append unselected context data as white lines
 +
                                        .datum(data)
 +
                                        .attr("class", "area_context_unselected")
 +
                                        .attr("d", area_context)
 +
                                        .style("stroke", "#ffffff");
 +
                                    context.append("path")
 +
                                        .datum(data)
 +
                                        .attr("class", "area_context")
 +
                                        .attr("d", area_context)
 +
                                        .style("stroke", "#1976D2");
 +
 
 +
                                    // Move Slider
 
                                     var t_svg = svg.transition()
 
                                     var t_svg = svg.transition()
 
                                         .duration(2000);
 
                                         .duration(2000);
Line 528: Line 657:
 
         var customEndDate = parseCustomDate( String(document.getElementById("customEndDate").value) );
 
         var customEndDate = parseCustomDate( String(document.getElementById("customEndDate").value) );
 
          
 
          
         if (customBeginDate == null) { var customBegin = data_start } else { var customBegin = customBeginDate };
+
         if (customBeginDate == null) { var customBegin = data_start } else { var customBegin = customBeginDate } // use data start as default
         if (customEndDate == null) { var customEnd = current_date } else { var customEnd = customEndDate };
+
         if (customEndDate == null) { var customEnd = current_date } else { var customEnd = customEndDate } // use current date as default
  
         if (customBegin > customEnd ) { customBegin = [customEnd, customEnd = customBegin][0] };
+
         if (customBegin > customEnd ) { customBegin = [customEnd, customEnd = customBegin][0] } // Switch fields if entered backwards
         if (customBegin-customEnd == 0) { customEnd = d3.timeDay.offset(customBegin, 1) };
+
         if (customBegin-customEnd == 0) { customEnd = d3.timeDay.offset(customBegin, 1) } // Add 1 day interval if fields are the same
 +
       
 +
        if (customBegin < timeslider_start) {    // Rescale the timeslider to accomodate custom range
 +
          xScale_context.domain( [ customBegin, current_date ] );
 +
          timeslider_start = customBegin
  
 +
          // REDRAW CONTEXT
 +
          context.selectAll("path").remove();
 +
          area_context.x(function(d) { return xScale_context(d.dateTime); });
 +
          context.append("path")  // append unselected context data as white lines
 +
              .datum(data)
 +
              .attr("class", "area_context_unselected")
 +
              .attr("d", area_context)
 +
              .style("stroke", "#ffffff");
 +
          context.append("path")
 +
              .datum(data)
 +
              .attr("class", "area_context")
 +
              .attr("d", area_context)
 +
              .style("stroke", "#1976D2");
 +
        }
 
         var t_svg = svg.transition()
 
         var t_svg = svg.transition()
 
             .duration(1000);
 
             .duration(1000);
Line 552: Line 699:
 
           .style("text-anchor", "front")
 
           .style("text-anchor", "front")
 
           .style("font-size", function(){return (String( $(window).width() / 900 )+"em") })
 
           .style("font-size", function(){return (String( $(window).width() / 900 )+"em") })
           .text("Select a Date Range:");
+
           .text("Selected Date Range:");
 +
 
 +
      context.append("text")
 +
          .attr("class", "label_date")
 +
          .attr("x", width_context)
 +
          .attr("y", 0)
 +
          .attr("dy", "-0.5em")
 +
          .style("text-anchor", "end")
 +
          .style("font-size", function(){return (String( $(window).width() / 900 )+"em") })
 +
          .text("  --  ");
  
 
       focus1.append("text")
 
       focus1.append("text")
Line 592: Line 748:
 
           .style("font-size", function(){return (String( $(window).width() / 900 )+"em") })
 
           .style("font-size", function(){return (String( $(window).width() / 900 )+"em") })
 
           .text("Wind Speed (m/s)");
 
           .text("Wind Speed (m/s)");
 +
 +
      focus2.append("text")
 +
          .attr("class", "label")
 +
          .attr("x", 40).attr("y", 20).style("text-anchor", "start")
 +
          .style("font-size", function(){return (String( $(window).width() / 1000 )+"em") })
 +
          .text("Data Out of Date Range");
 +
 +
      focus2.append("rect")
 +
          .attr("x", 20).attr("y", 8)
 +
          .style("fill", "white")
 +
          .style("stroke", "#000")
 +
          .style("stroke-width", "0.5")
 +
          .attr("width", "12")
 +
          .attr("height", "12");
  
 
       // Grid
 
       // Grid
Line 619: Line 789:
 
           .scale(width_context / (s[1] - s[0]))
 
           .scale(width_context / (s[1] - s[0]))
 
           .translate(-s[0], 0));
 
           .translate(-s[0], 0));
 +
      svg.select("#context_data_clip").select("rect")  // Modify clipping path for selected data in context time slider
 +
        .attr("x", s[0])
 +
        .attr("width", s[1] - s[0]);
 +
      svg.select(".label_date").text(String(xScale_context.invert(s[0])).slice(0,21) + "  --  " + String(xScale_context.invert(s[1])).slice(0,21));    // Show the exact timestamps being focused on
 +
      svg.select(".leftHandle").attr("x", s[0]); svg.select(".rightHandle").attr("x", s[1]-8); // Move handles
 
     };
 
     };
  
Line 628: Line 803:
 
       focus1.select(".axis--x").call(xAxis_focus1);    // Updates focus based on all activity
 
       focus1.select(".axis--x").call(xAxis_focus1);    // Updates focus based on all activity
 
       context.select(".brush").call(brush.move, xScale_context.range().map(t.invertX, t));    // Updates the Selector based on Focus motion
 
       context.select(".brush").call(brush.move, xScale_context.range().map(t.invertX, t));    // Updates the Selector based on Focus motion
 +
      svg.select("#context_data_clip").select("rect")
 +
        .attr("x", xScale_context.range().map(t.invertX, t)[0])
 +
        .attr("width", xScale_context.range().map(t.invertX, t)[1] - xScale_context.range().map(t.invertX, t)[0]);
 +
      svg.select(".label_date").text(String(xScale_context.invert(xScale_context.range().map(t.invertX, t)[0])).slice(0,21) + "  --  " + String(xScale_context.invert(xScale_context.range().map(t.invertX, t)[1])).slice(0,21));    // Show the exact timestamps being focused on
 +
      svg.select(".leftHandle").attr("x", xScale_context.range().map(t.invertX, t)[0]); svg.select(".rightHandle").attr("x", xScale_context.range().map(t.invertX, t)[1]-8); // Move handles
 
     };
 
     };
  

Revision as of 08:14, 8 September 2017

This widget provides visualizations for an individual Wind for Schools Portal Project's page. It generates an interactive power timeseries graph and turbine performance curve.

For example:

{{#Widget:WFSPProject}}