Advertisement
codemonkey

c3js-chart-widget.coffee

May 9th, 2016
300
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. BLOCK_HEIGHT = 62
  3.  
  4. freeboard.loadWidgetPlugin
  5.     "type_name": "line_chart"
  6.     "display_name": "Line Chart"
  7.     "description": "Line Chart widget powered by C3.js, " +
  8.                    "written by Tomas Sandven"
  9.     "external_scripts": [
  10.         "https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js",
  11.         "https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.js"
  12.     ]
  13.     "fill_size": true
  14.     "settings": [
  15.         {
  16.             name: "height",
  17.             display_name: "Height (in blocks)"
  18.             type: "number"
  19.             default_value: 5
  20.         }
  21.         {
  22.             name: "columns"
  23.             display_name: "Columns"
  24.             type: "calculated"
  25.         }
  26.         {
  27.             name: "flow"
  28.             display_name: "Flow data"
  29.             type: "boolean"
  30.             default: false
  31.             description: """
  32.                Displays a neat animation when data flows from right to left.
  33.                Only set this to true if past data never changes.
  34.            """
  35.         }
  36.         {
  37.             name: "chart_type"
  38.             display_name: "Chart type"
  39.             type: "option"
  40.             default_value: "area"
  41.             options: [
  42.                 {
  43.                     name: "Area chart"
  44.                     value: "area"
  45.                 }
  46.                 {
  47.                     name: "Bar chart"
  48.                     value: "bar"
  49.                 }
  50.                 {
  51.                     name: "Line chart"
  52.                     value: "line"
  53.                 }
  54.                 {
  55.                     name: "Pie chart"
  56.                     value: "pie"
  57.                 }
  58.                 {
  59.                     name: "Donut chart"
  60.                     value: "donut"
  61.                 }
  62.             ]
  63.         }
  64.         {
  65.             name: "legend_position"
  66.             display_name: "Legend position"
  67.             type: "option"
  68.             default_value: "bottom"
  69.             options: [
  70.                 {
  71.                     name: "Bottom"
  72.                     value: "bottom"
  73.                 }
  74.                 {
  75.                     name: "Right"
  76.                     value: "right"
  77.                 }
  78.                 {
  79.                     name: "Inset"
  80.                     value: "inset"
  81.                 }
  82.             ]
  83.         }
  84.         {
  85.             name: "time_format"
  86.             display_name: "X Axis Time Format"
  87.             type: "text"
  88.             default_value: "%Y-%m-%d %H:%M:%S"
  89.         }
  90.         {
  91.             name: "display_markers"
  92.             display_name: "Display Time Markers"
  93.             type: "boolean"
  94.         }
  95.         {
  96.             name: "marker_interval"
  97.             display_name: "Time Markers: Interval"
  98.             type: "option"
  99.             options: [
  100.                 {
  101.                     name: "Every 30 seconds"
  102.                     value: 30
  103.                 }
  104.                 {
  105.                     name: "Every minute"
  106.                     value: 60
  107.                 }
  108.                 {
  109.                     name: "Every 10 minutes"
  110.                     value: 60 * 10
  111.                 }
  112.                 {
  113.                     name: "Every 30 minutes"
  114.                     value: 60 * 30
  115.                 }
  116.                 {
  117.                     name: "Every hour"
  118.                     value: 60 * 60
  119.                 }
  120.                 {
  121.                     name: "Every 2 hours"
  122.                     value: 60 * 60 * 2
  123.                 }
  124.                 {
  125.                     name: "Every 3 hours"
  126.                     value: 60 * 60 * 3
  127.                 }
  128.                 {
  129.                     name: "Every 4 hours"
  130.                     value: 60 * 60 * 4
  131.                 }
  132.                 {
  133.                     name: "Every 6 hours"
  134.                     value: 60 * 60 * 6
  135.                 }
  136.                 {
  137.                     name: "Every day"
  138.                     value: 60 * 60 * 24
  139.                 }
  140.                 {
  141.                     name: "Every 3 days"
  142.                     value: 60 * 60 * 24 * 3
  143.                 }
  144.             ]
  145.         }
  146.         {
  147.             name: "marker_time_format"
  148.             display_name: "Time Markers: Format"
  149.             type: "text"
  150.             default_value: "%Y-%m-%d %H:%M:%S"
  151.         }
  152.     ]
  153.  
  154.     "newInstance": (settings, newInstanceCallback) ->
  155.         newInstanceCallback(new LineChartWidget(settings))
  156.  
  157. class LineChartWidget
  158.     constructor: (settings) ->
  159.         #console.log "Initializing LineChartWidget with settings", settings
  160.  
  161.         @setSettings(settings)
  162.  
  163.     setSettings: (newSettings) =>
  164.         oldSettings = @settings
  165.         @settings = newSettings
  166.  
  167.         if @chart
  168.             size =
  169.                 width: @chartElement.outerWidth()
  170.                 height: @settings["height"] * BLOCK_HEIGHT
  171.  
  172.             if @settings["size"] != size
  173.                 @chart.resize size
  174.  
  175.             if oldSettings["chart_type"] != newSettings["chart_type"]
  176.                 @chart.transform @settings["chart_type"]
  177.  
  178.             if oldSettings["legend_position"] != newSettings["legend_position"]
  179.                 @chart.destroy()
  180.                 @chart = @createChart()
  181.  
  182.         @updateTimeMarkers()
  183.  
  184.     createChart: =>
  185.         # Groups causes flow to bug out. That needs to be worked around before
  186.         # groups can be used.
  187.         #groups = [x[0] for x in @columns[1..]]
  188.  
  189.         chart = c3.generate
  190.             bindto: @chartElement[0]
  191.             size:
  192.                 width: @chartElement.outerWidth()
  193.                 height: Number(@settings["height"]) * BLOCK_HEIGHT
  194.             data:
  195.                 type: @settings["chart_type"]
  196.                 x: "x"
  197.                 xFormat: @settings["time_format"]
  198.                 columns: @columns
  199.                 #groups: groups
  200.             axis:
  201.                 x:
  202.                     type: "timeseries"
  203.                     show: false
  204.                     tick:
  205.                         format: "%Y-%m-%d %H:%M:%S"
  206.                         culling: true
  207.                         fit: true
  208.                         rotate: 45
  209.             legend:
  210.                 position: @settings["legend_position"]
  211.  
  212.             # Performance options
  213.             interaction:
  214.                 enabled: false
  215.             transition:
  216.                 duration: 0
  217.  
  218.         return chart
  219.  
  220.     updateChart: (oldColumns, newColumns) =>
  221.         if oldColumns == newColumns
  222.             return
  223.  
  224.         if @settings["flow"]
  225.             @flowChartData(oldColumns, newColumns)
  226.         else
  227.             @chart.load({"columns": newColumns, "unload": true})
  228.  
  229.     flowChartData: (oldColumns, newColumns) =>
  230.         additions = []
  231.  
  232.         $(newColumns).each (index, column) ->
  233.             additions.push(column.slice(0))
  234.  
  235.         # The first column is assumed to be the X axis
  236.         xColumn = newColumns[0]
  237.         xColumnOld = oldColumns[0]
  238.  
  239.         # The amount of elements to be deleted from the new array
  240.         deleteCount = xColumn.length - 1
  241.  
  242.         $(xColumn.slice(0).reverse()).each (index, value) ->
  243.             # Skip the last item (the column name)
  244.             if index == xColumn.length - 1
  245.                 return false
  246.  
  247.             if value not in xColumnOld
  248.                 deleteCount -= 1
  249.             else
  250.                 return false
  251.  
  252.             return null
  253.  
  254.         $(additions).each (index, value) ->
  255.             value.splice(1, deleteCount)
  256.  
  257.         @chart.flow
  258.             columns: additions
  259.  
  260.     updateTimeMarkers: =>
  261.         return if not @chart
  262.  
  263.         if not @settings["display_markers"]
  264.             @chart.xgrids.remove()
  265.             return
  266.  
  267.         #console.group("updateTimeMarkers")
  268.  
  269.         parse = d3.time.format(@settings["time_format"]).parse
  270.         format = d3.time.format(@settings["marker_time_format"])
  271.         tzOffset = (new Date().getTimezoneOffset() * 60)
  272.  
  273.         firstDate = parse(@columns[0][1])
  274.         lastDate = parse(@columns[0][@columns[0].length - 1])
  275.  
  276.         #console.log "First date", firstDate
  277.         #console.log "Last date", lastDate
  278.  
  279.         first = firstDate.valueOf() / 1000
  280.         last = lastDate.valueOf() / 1000
  281.  
  282.         interval = Number(@settings["marker_interval"])
  283.  
  284.         overflow = (first % interval)
  285.         firstMark = (first - overflow)
  286.         firstMarkDate = new Date(firstMark * 1000)
  287.  
  288.         overflow = ((last + interval) % interval)
  289.         lastMark = ((last + interval) - overflow)
  290.         lastMarkDate = new Date(lastMark * 1000)
  291.  
  292.         #console.log "First mark", firstMarkD6
  293.         #console.log "Last mark", lastMarkDate
  294.  
  295.         marks = []
  296.  
  297.         # The cursor is aligned to the start of the day. This allows uneven
  298.         # intervals like 6 hours to start from 00:00 rather than (for instance)
  299.         # 02:00.
  300.         day = 60 * 60 * 24
  301.         cursor = (firstMark - (firstMark % day))
  302.  
  303.         #console.log "Cursor starts at:", new Date(cursor * 1000)
  304.  
  305.         while true
  306.             break if cursor > lastMark
  307.  
  308.             if cursor < firstMark
  309.                 cursor += interval
  310.                 continue
  311.  
  312.             # If the interval is greater than an hour, the effect of the local
  313.             # time zone has to be counteracted
  314.             if interval > 3600
  315.                 date = new Date((cursor + tzOffset) * 1000)
  316.             else
  317.                 date = new Date(cursor * 1000)
  318.  
  319.             marks.push
  320.                 value: date
  321.                 text: format(date)
  322.  
  323.             cursor += interval
  324.  
  325.         #console.log "New marks generated:", marks
  326.  
  327.         @chart.xgrids marks
  328.  
  329.         #console.groupEnd()
  330.  
  331.         return null
  332.  
  333.     displayLoading: =>
  334.         @container.empty()
  335.  
  336.         textContainer = $("<div>")
  337.             .css
  338.                 "display": "flex",
  339.                 "height": @settings["height"] * BLOCK_HEIGHT
  340.                 "justify-content": "center"
  341.                 "align-items": "center"
  342.                 "font-size": "2em"
  343.             .appendTo(@container)
  344.  
  345.         text = $("<div>")
  346.             .text("Loading data...")
  347.             .appendTo(textContainer)
  348.  
  349.     #
  350.     # API
  351.     #
  352.  
  353.     render: (containerElement) =>
  354.         if containerElement
  355.             @container = $(containerElement)
  356.  
  357.         if not @columns
  358.             @displayLoading()
  359.             return null
  360.  
  361.         if not @chart
  362.             @container.empty()
  363.  
  364.             @chartElement = $("<div>")
  365.             @container.append(@chartElement)
  366.  
  367.             @chart = @createChart()
  368.  
  369.             @updateTimeMarkers()
  370.  
  371.         return null
  372.  
  373.     getHeight: =>
  374.         return @settings["height"]
  375.  
  376.     onSettingsChanged: (newSettings) =>
  377.         @setSettings(newSettings)
  378.  
  379.         return null
  380.  
  381.     onCalculatedValueChanged: (settingName, newValue) =>
  382.         if settingName == "columns"
  383.             oldColumns = @columns
  384.             @columns = newValue
  385.  
  386.             # First render? It's necessary to wait for a bit before drawing the
  387.             # chart so the opening animations can finish.
  388.             if not oldColumns
  389.                 setTimeout @render, 1000
  390.             else
  391.                 @updateChart(oldColumns, @columns)
  392.  
  393.         @updateTimeMarkers()
  394.  
  395.         return null
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement