コード例 #1
0
class SlowControlGUI(flx.PyComponent):
    led = flx.IntProp(0, settable=True)
    hv = flx.IntProp(0, settable=True)
    vsel = flx.IntProp(0, settable=True)

    chillerStat = flx.IntProp(0, settable=True)
    chillerSwitch = flx.IntProp(0, settable=True)
    tempchiller = flx.FloatProp(0, settable=True)
    tempbench = flx.FloatProp(0, settable=True)
    tempext = flx.FloatProp(0, settable=True)
    humbench = flx.FloatProp(0, settable=True)
    humext = flx.FloatProp(0, settable=True)

#    sercon =  flx.ComponentProp()
#   serconChiller =  flx.ComponentProp()
#    serconSensirion =  flx.ComponentProp()
#    ledcon =  flx.ComponentProp()

    initialised = 0

    def init(self):
        super().init()
        #        self._mutate_sercon(SerialConnection('/dev/ttyACM1'))
        self.sercon = SerialConnection('/dev/sensor_0')
        #        self._mutate_serconChiller(SerialConnectionChiller('/dev/ttyUSB0'))
        self.serconChiller = SerialConnectionChiller('/dev/ttyUSB0')
        #        self._mutate_serconSensirion(SerialConnectionSensirion('/dev/ttyACM0'))
        self.serconSensirion = SerialConnectionSensirion('/dev/sensor_1') 
        #        self._mutate_ledcon(LedPulser())
        self.ledcon = LedPulser() 
        self.hvcon = SerialConnectionHV('/dev/ttyUSB4')

        self.t0 = datetime.now()
        #Always start in inhbit mode
        reply=self.sercon.sendCommand('i')
        if (reply!='INH'):
            print('Please check serial connection')

        reply=self.sercon.sendCommand('0')
        if (reply!='V0'):
            print('Please check serial connection')

        ret=self.ledcon.ledSwitch(0)
        if (ret!=0):
            print('Please check led pulser connection')

#        with flx.HBox(flex=0, spacing=10):

        with flx.PinboardLayout():
            self.tempText = flx.Label(text='T: XX.XX H: YY.YY',style='left:10px; top:120px; width:300px;height:20px;')
            self.sensirionText = flx.Label(text='T_EXT:XXX.XX H_EXT:YYY.YY DEW_EXT:ZZZ.ZZ',style='left:10px; top:140px; width:400px;height:20px;')
            self.hvText = flx.Label(text='VSET:XXX.XX VMON:XXX.XX',style='left:10px; top:180px; width:300px;height:20px;')
            self.chillerText = flx.Label(text='CHILLER TBATH:XXX.XX TSET:YYY.YY PUMP:ZZ',style='left:10px; top:160px; width:400px;height:20px;')
            self.but1 = flx.Button(text='Led Pulser',css_class="border-black",style='left:10px; top:10px; width:180px;height:100px;')
            self.but2 = flx.Button(text='HV',css_class="border-black",style='left:200px; top:10px; width:150px;height:100px;')
            self.but3 = flx.Button(text='VSEL',css_class="border-black",style='left:360px; top:10px; width:100px;height:100px;')
            self.chillerStatus = flx.Button(text='COOLING OK',css_class="border-black",style='left:470px; top:10px; width:250px;height:100px;')
#            self.label = flx.Label(text='', flex=1)  # take all remaining space

        self.refreshTemperature()
        self.refreshChiller()
        self.refreshSensirion()
        self.refreshHV()
#upload to things speak (every 60s)
        self.refreshThingSpeak()

        self.initialised=1

    @flx.action
    def refreshTemperature(self):
        if (self.initialised != 0):
            reply=self.sercon.sendCommand('t')
            self.tempText.set_text(reply)
            logging.info(reply)
            temp=reply.split(' ')[1]
            self._mutate_tempbench(temp)
            hum=reply.split(' ')[3]
            self._mutate_humbench(hum)
        asyncio.get_event_loop().call_later(5, self.refreshTemperature)

    @flx.action
    def refreshSensirion(self):
        if (self.initialised != 0):
            reply=self.serconSensirion.sendCommand('t')
            if (len(reply.split(' ')) == 10):
                temp=reply.split(' ')[1]
                self._mutate_tempext(temp)
                hum=reply.split(' ')[7]
                self._mutate_humext(hum)
                dew=reply.split(' ')[9]
                self.sensirionText.set_text('T_EXT:'+temp+' H_EXT:'+hum+' DEW_EXT:'+dew)
                logging.info('T_EXT:'+temp+' H_EXT:'+hum+' DEW_EXT:'+dew)

        asyncio.get_event_loop().call_later(6, self.refreshSensirion)

    @flx.action
    def refreshHV(self):
        if (self.initialised != 0):
           vmon=self.hvcon.sendCommand('m')
           vset=self.hvcon.sendCommand('s')
           stat=self.hvcon.sendCommand('i')
           self.hvText.set_text('VSET:'+vset+' VMON:'+vmon)
           logging.info('VSET:'+vset+' VMON:'+vmon+' STATUS:'+stat)
           if ( (int(stat) & 1) == 1):
               self._mutate_hv(1)
           elif ( (int(stat) & 1) == 0):
               self._mutate_hv(0)
        asyncio.get_event_loop().call_later(7, self.refreshHV)

    @flx.action
    def refreshChiller(self):
        try:
            if (self.initialised != 0):
                tbath=self.serconChiller.sendCommand('t')
                #            print(tbath)
                tset=self.serconChiller.sendCommand('g')
                pump=self.serconChiller.sendCommand('p')
                status=self.serconChiller.sendCommand('s')
                self.chillerText.set_text('CHILLER TBATH:'+tbath+' TSET:'+tset+' PUMP:'+pump)

                if (pump== '000.00' and status == '0'):
                    self.chillerStatus.set_text('COOLING OFF')
                    self.chillerStatus.apply_style('background:yellow;')
                    self._mutate_chillerSwitch(0)
                elif (status == '0' and pump== '003.00'):
                    self.chillerStatus.set_text('COOLING OK')
                    self.chillerStatus.apply_style('background:green;')
                    self._mutate_chillerStat(1)
                    self._mutate_chillerSwitch(1)
                else:
                    self.chillerStatus.set_text('COOLING KO')
                    self.chillerStatus.apply_style('background:red;')
                    self._mutate_chillerStat(0)

                if not 'ERR' in tbath: 
                    self._mutate_tempchiller(float(tbath))

                logging.info('CHILLER TBATH: '+tbath+' STATUS: '+status+' TSET:'+tset+' PUMP:'+pump)
        except:
                logging.info('Got an exception')

        asyncio.get_event_loop().call_later(10, self.refreshChiller)

    def refreshThingSpeak(self):
        if (self.initialised != 0):
            params = {'field1': self.tempbench, 'field2': self.humbench, 'field3': self.tempchiller, 'field4': self.chillerStat, 'field5': self.tempext, 'field6': self.humext, 'key': thingspeak_key}
            logThingSpeak(params)

        asyncio.get_event_loop().call_later(60, self.refreshThingSpeak)

    @flx.action
    def hv_switch(self):
        if (self.hv==0):
            reply=self.hvcon.sendCommand('o')
            if (reply=='OK'):
                self._mutate_hv(1)
        elif (self.hv==1):
            reply=self.hvcon.sendCommand('k')
            if (reply=='OK'):
                self._mutate_hv(0)

    @flx.action
    def led_switch(self):
        if (self.led==0):
            ret=self.ledcon.ledSwitch(1)
            if (ret==0):
                self._mutate_led(1)
        elif (self.led==1):
            ret=self.ledcon.ledSwitch(0)
            if (ret==0):
                self._mutate_led(0)

    @flx.action
    def chiller_switch(self):
        if (self.chillerSwitch==0):
            reply=self.serconChiller.sendCommand('o')
            if (reply=='OK'):
                self._mutate_chillerSwitch(1)
        elif (self.chillerSwitch==1):
            reply=self.serconChiller.sendCommand('k')
            if (reply=='OK'):
                self._mutate_chillerSwitch(0)
        
    @flx.action
    def vsel_switch(self):
        if (self.vsel==0):
            reply=self.sercon.sendCommand('1')
            if (reply=='V1'):
                self._mutate_vsel(1)
        elif (self.vsel==1):
            reply=self.sercon.sendCommand('0')
            if (reply=='V0'):
                self._mutate_vsel(0)

    @flx.reaction('but1.pointer_click')
    def but1_clicked(self, *events):
        self.led_switch()

    @flx.reaction('but2.pointer_click')
    def but2_clicked(self, *events):
        self.hv_switch()

    @flx.reaction('but3.pointer_click')
    def but3_clicked(self, *events):
        self.vsel_switch()

    @flx.reaction('chillerStatus.pointer_click')
    def chillerButton_clicked(self, *events):
        self.chiller_switch()

#    @flx.reaction
#    def update_label(self, *events):
 
    @flx.reaction
    def update_buttons(self, *events):
        if (self.hv==1):
            self.but2.set_text('HV ON')
            self.but2.apply_style('background:red;')
        if (self.hv==0):
            self.but2.set_text('HV OFF')
            self.but2.apply_style('background:yellow;')
        if (self.led==1):
            self.but1.set_text('LED ON')
            self.but1.apply_style('background:red;')
        if (self.led==0):
            self.but1.set_text('LED OFF')
            self.but1.apply_style('background:yellow;')
        if (self.chillerSwitch==0):
            self.chillerStatus.set_text('COOLING OFF')
            self.chillerStatus.apply_style('background:yellow;')
        if (self.chillerSwitch==1 and self.chillerStat==0):
            self.chillerStatus.set_text('COOLING ON')
            self.chillerStatus.apply_style('background:orange;')
        if (self.chillerSwitch==1 and self.chillerStat==1):
            self.chillerStatus.set_text('COOLING OK')
            self.chillerStatus.apply_style('background:green;')
        if (self.vsel==1):
            self.but3.set_text('V1')
        if (self.vsel==0):
            self.but3.set_text('V0')
コード例 #2
0
class Relay(flx.Component):
    #	def init(self):
    #		super().init()
    #		self.time = "Relay Default Time"
    #		self.newTime = False
    #
    #	def setTime(self, time):
    #		self.time = time
    #		self.newTime = True
    vin = flx.StringProp("", settable=True)
    timeString = flx.StringProp("", settable=True)
    voltage = flx.FloatProp(0, settable=True)
    capacity = flx.FloatProp(0, settable=True)
    pandaRecording = flx.IntProp(0, settable=True)
    pandaGps = flx.IntProp(0, settable=True)
    vehicleControlRunning = flx.BoolProp(False, settable=True)

    @flx.reaction('vin')
    def on_vin(self, *events):
        for ev in events:
            self.updateVin(ev.new_value)

    @flx.reaction('timeString')
    def on_timeString(self, *events):
        for ev in events:
            self.updateTime(ev.new_value)

    @flx.reaction('voltage')
    def on_voltage(self, *events):
        for ev in events:
            self.updateVoltage(ev.new_value)

    @flx.reaction('capacity')
    def on_voltage(self, *events):
        for ev in events:
            self.updateCapacity(ev.new_value)

    @flx.reaction('pandaRecording')
    def on_pandaRecording(self, *events):
        for ev in events:
            self.updatePandaRecording(ev.new_value)

    @flx.reaction('pandaGps')
    def on_pandaGps(self, *events):
        for ev in events:
            self.updatePandaGps(ev.new_value)

    @flx.reaction('vehicleControlRunning')
    def on_vehicleControlRunning(self, *events):
        for ev in events:
            self.updateVehicleControlRunning(ev.new_value)

    """ Global object to relay paint events to all participants.
	"""

    @flx.emitter
    def updateVin(self, value):
        return dict(value=value)

    @flx.emitter
    def updateTime(self, value):
        return dict(value=value)

    @flx.emitter
    def updateVoltage(self, value):
        return dict(value=value)

    @flx.emitter
    def updateCapacity(self, value):
        return dict(value=value)

    @flx.emitter
    def updatePandaRecording(self, value):
        return dict(value=value)

    @flx.emitter
    def updatePandaGps(self, value):
        return dict(value=value)

    @flx.emitter
    def updateVehicleControlRunning(self, value):
        return dict(value=value)
コード例 #3
0
ファイル: shading_gui.py プロジェクト: Ailrk-sArchives/epMOO
class Run(flx.PyWidget):
    exterior_wall_tuple = flx.TupleProp((1, 10), settable=True)
    exterior_roof_tuple = flx.TupleProp((1, 9), settable=True)
    exterior_window_tuple = flx.TupleProp((1, 3), settable=True)
    eastrate_tuple = flx.TupleProp((0.05, 0.3), settable=True)
    westrate_tuple = flx.TupleProp((0.05, 0.3), settable=True)
    southrate_tuple = flx.TupleProp((0.05, 0.3), settable=True)
    northrate_tuple = flx.TupleProp((0.05, 0.3), settable=True)
    direction_tuple = flx.TupleProp((0, 359), settable=True)
    airchange_tuple = flx.TupleProp((0, 39), settable=True)
    cop_tuple = flx.TupleProp((1, 4), settable=True)
    east_shading_tuple = flx.TupleProp((0, 1), settable=True)
    west_shading_tuple = flx.TupleProp((0, 1), settable=True)
    south_shading_tuple = flx.TupleProp((0, 1), settable=True)
    north_shading_tuple = flx.TupleProp((0, 1), settable=True)
    infiltration_airchange_tuple = flx.TupleProp((1, 3), settable=True)

    mutation_param_int = flx.IntProp(4, settable=True)
    num_of_generation_int = flx.IntProp(50, settable=True)
    num_of_individual_int = flx.IntProp(50, settable=True)
    num_of_tour_particps_int = flx.IntProp(2, settable=True)
    max_proc_int = flx.IntProp(8, settable=True)

    floor_height_float = flx.FloatProp(2.8, settable=True)
    window_height_float = flx.FloatProp(1.5, settable=True)
    window_edge_height_float = flx.FloatProp(1, settable=True)
    heating_setpoint_float = flx.FloatProp(18, settable=True)
    cooling_setpoint_float = flx.FloatProp(26, settable=True)
    apmv_upper_float = flx.FloatProp(0.5, settable=True)
    apmv_lower_float = flx.FloatProp(-0.5, settable=True)

    weather_file_str = flx.StringProp(
        '../WeatherData/CHN_Chongqing.Chongqing.Shapingba.575160_CSWD.epw',
        settable=True)
    idf_file_str = flx.StringProp('shading_model_6-0603-1.idf', settable=True)
    output_path_str = flx.StringProp('temp/', settable=True)

    def init(self):
        self.moo = MooWidget(self)

    @flx.action
    def run_moo(self):
        # the actual range used by the algorithm is set here.
        paras = [
            discrete_interval(tuple(self.moo.root.exterior_wall_tuple)),
            discrete_interval(tuple(self.moo.root.exterior_roof_tuple)),
            discrete_interval(tuple(self.moo.root.exterior_window_tuple)),
            tuple(self.moo.root.eastrate_tuple),
            tuple(self.moo.root.westrate_tuple),
            tuple(self.moo.root.southrate_tuple),
            tuple(self.moo.root.northrate_tuple),
            tuple(self.moo.root.direction_tuple),
            tuple(self.moo.root.airchange_tuple),
            discrete_interval(tuple(self.moo.root.cop_tuple)),
            discrete_interval(tuple(self.moo.root.east_shading_tuple)),
            discrete_interval(tuple(self.moo.root.west_shading_tuple)),
            discrete_interval(tuple(self.moo.root.south_shading_tuple)),
            discrete_interval(tuple(self.moo.root.north_shading_tuple)),
            discrete_interval(tuple(
                self.moo.root.infiltration_airchange_tuple))
        ]
        """Algorithm parameter"""
        hyperparameter: Dict = {
            "MUTATION_PARAM": int(self.moo.root.mutation_param_int),
            "NUM_OF_GENERATIONS": int(self.moo.root.num_of_generation_int),
            "NUM_OF_INDIVIDUALS": int(self.moo.root.num_of_individual_int),
            "NUM_OF_TOUR_PARTICIPS":
            int(self.moo.root.num_of_tour_particps_int),
            "CONCURRENCY": True,
            "MAX_PROC": int(self.moo.root.max_proc_int)
        }
        """other constants"""
        constants: Dict = {
            "FLOOR_HEIGHT": float(self.moo.root.floor_height_float),
            "WINDOW_HEIGHT": float(self.moo.root.window_height_float),
            "WINDOW_EDG_HEIGHT": float(self.moo.root.window_edge_height_float),
            "HEATING_SETPOINT": float(self.moo.root.heating_setpoint_float),
            "COOLING_SETPOINT": float(self.moo.root.cooling_setpoint_float),
            "APMV_UPPER": float(self.moo.root.apmv_upper_float),
            "APMV_LOWER": float(self.moo.root.apmv_lower_float)
        }
        """path constants"""
        paths: Dict = {
            "WEATHER_FILE": str(self.moo.root.weather_file_str),
            "IDF_FILE": str(self.moo.root.idf_file_str),
            "OUTPUT_PATH": str(self.moo.root.output_path_str)
        }

        __import__('pprint').pprint(paras)
        __import__('pprint').pprint(hyperparameter)
        __import__('pprint').pprint(constants)
        __import__('pprint').pprint(paths)

        moo_run(paras, hyperparameter, constants, paths)

    @flx.action
    def stop(self):
        sys.exit(0)
コード例 #4
0
class SplineWidget(flx.CanvasWidget):

    spline_type = flx.EnumProp(SPLINES,
                               'cardinal',
                               settable=True,
                               doc="""
        "The type of spline
        """)

    closed = flx.BoolProp(False,
                          settable=True,
                          doc="""
        Whether the spline is closed
        """)

    tension = flx.FloatProp(0.5,
                            settable=True,
                            doc="""
        The tension parameter for the Cardinal spline.
        """)

    _current_node = flx.Property(None, settable=True)

    def init(self):
        self.ctx = self.node.getContext('2d')
        self.xx = [0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.10, 0.23, 0.61, 0.88]
        self.yy = [0.90, 0.60, 0.90, 0.60, 0.90, 0.70, 0.55, 0.19, 0.11, 0.38]

    def factors_linear(self, t):
        return [0, t, (1 - t), 0]

    def factors_basis(self, t):
        f0 = (1 - t)**3 / 6.0
        f1 = (3 * t**3 - 6 * t**2 + 4) / 6.0
        f2 = (-3 * t**3 + 3 * t**2 + 3 * t + 1) / 6.0
        f3 = t**3 / 6.0
        return f0, f1, f2, f3

    def factors_cardinal(self, t):
        tension = self.tension
        tau = 0.5 * (1 - tension)
        f0 = -tau * (t**3 - 2 * t**2 + t)
        f3 = +tau * (t**3 - 1 * t**2)
        f1 = 2 * t**3 - 3 * t**2 + 1 - f3
        f2 = -2 * t**3 + 3 * t**2 - f0
        return f0, f1, f2, f3

    def factors_catmullrom(self, t):
        f0 = -0.5 * t**3 + 1.0 * t**2 - 0.5 * t
        f1 = +1.5 * t**3 - 2.5 * t**2 + 1
        f2 = -1.5 * t**3 + 2.0 * t**2 + 0.5 * t
        f3 = +0.5 * t**3 - 0.5 * t**2
        return f0, f1, f2, f3

    def factors_lagrange(self, t):
        k = -1.0
        f0 = t / k * (t - 1) / (k - 1) * (t - 2) / (k - 2)
        k = 0
        f1 = (t + 1) / (k + 1) * (t - 1) / (k - 1) * (t - 2) / (k - 2)
        k = 1
        f2 = (t + 1) / (k + 1) * t / k * (t - 2) / (k - 2)
        k = 2
        f3 = (t + 1) / (k + 1) * t / k * (t - 1) / (k - 1)
        return f0, f1, f2, f3

    def factors_lanczos(self, t):
        sin = window.Math.sin
        pi = window.Math.PI
        tt = (1 + t)
        f0 = 2 * sin(pi * tt) * sin(pi * tt / 2) / (pi * pi * tt * tt)
        tt = (2 - t)
        f3 = 2 * sin(pi * tt) * sin(pi * tt / 2) / (pi * pi * tt * tt)
        if t != 0:
            tt = t
            f1 = 2 * sin(pi * tt) * sin(pi * tt / 2) / (pi * pi * tt * tt)
        else:
            f1 = 1
        if t != 1:
            tt = (1 - t)
            f2 = 2 * sin(pi * tt) * sin(pi * tt / 2) / (pi * pi * tt * tt)
        else:
            f2 = 1
        return f0, f1, f2, f3

    @flx.reaction('pointer_down')
    def _on_pointer_down(self, *events):
        for ev in events:
            w, h = self.size
            # Get closest point
            closest, dist = -1, 999999
            for i in range(len(self.xx)):
                x, y = self.xx[i] * w, self.yy[i] * h
                d = ((x - ev.pos[0])**2 + (y - ev.pos[1])**2)**0.5
                if d < dist:
                    closest, dist = i, d
            # Did we touch it or not
            if dist < 9:
                i = closest
                if 'Shift' in ev.modifiers:  # Remove point
                    self.xx.pop(i)
                    self.yy.pop(i)
                    self._set_current_node(None)
                    self.update()
                else:
                    self._set_current_node(i)
            else:
                if 'Shift' in ev.modifiers:
                    # Add point
                    if not self.xx:
                        i = 0  # There were no points
                    else:
                        # Add in between two points. Compose the vectors
                        # from closest points to neightbour points and to the
                        # cicked point. Check with which vector the latter vector
                        # aligns the best by calculating their angles.
                        #
                        # Get the three points
                        p0 = self.xx[closest + 0] * w, self.yy[closest + 0] * h
                        if closest == 0:
                            p2 = self.xx[closest + 1] * w, self.yy[closest +
                                                                   1] * h
                            p1 = p0[0] - (p2[0] - p0[0]), p0[1] - (p2[1] -
                                                                   p0[1])
                        elif closest == len(self.xx) - 1:
                            p1 = self.xx[closest - 1] * w, self.yy[closest -
                                                                   1] * h
                            p2 = p0[0] - (p1[0] - p0[0]), p0[1] - (p1[1] -
                                                                   p0[1])
                        else:
                            p1 = self.xx[closest - 1] * w, self.yy[closest -
                                                                   1] * h
                            p2 = self.xx[closest + 1] * w, self.yy[closest +
                                                                   1] * h
                        # Calculate vectors, and normalize
                        v1 = p1[0] - p0[0], p1[1] - p0[1]
                        v2 = p2[0] - p0[0], p2[1] - p0[1]
                        v3 = ev.pos[0] - p0[0], ev.pos[1] - p0[1]
                        m1 = (v1[0]**2 + v1[1]**2)**0.5
                        m2 = (v2[0]**2 + v2[1]**2)**0.5
                        m3 = (v3[0]**2 + v3[1]**2)**0.5
                        v1 = v1[0] / m1, v1[1] / m1
                        v2 = v2[0] / m2, v2[1] / m2
                        v3 = v3[0] / m3, v3[1] / m3
                        # Calculate angle
                        a1 = window.Math.acos(v1[0] * v3[0] + v1[1] * v3[1])
                        a2 = window.Math.acos(v2[0] * v3[0] + v2[1] * v3[1])
                        i = closest if a1 < a2 else closest + 1
                    self.xx.insert(i, ev.pos[0] / w)
                    self.yy.insert(i, ev.pos[1] / h)
                    self._set_current_node(i)

    @flx.reaction('pointer_up')
    def _on_pointer_up(self, *events):
        self._set_current_node(None)

    @flx.reaction('pointer_move')
    def _on_pointer_move(self, *events):
        ev = events[-1]
        if self._current_node is not None:
            i = self._current_node
            w, h = self.size
            self.xx[i] = ev.pos[0] / w
            self.yy[i] = ev.pos[1] / h
            self.update()

    @flx.reaction('size', 'spline_type', 'tension', 'closed', '_current_node')
    def update(self, *events):

        # Init
        ctx = self.ctx
        w, h = self.size
        ctx.clearRect(0, 0, w, h)

        # Get coordinates
        xx = [x * w for x in self.xx]
        yy = [y * h for y in self.yy]
        #
        if self.closed:
            xx = xx[-1:] + xx + xx[:2]
            yy = yy[-1:] + yy + yy[:2]
        else:
            xx = [xx[0] - (xx[1] - xx[0])] + xx + [xx[-1] - (xx[-2] - xx[-1])]
            yy = [yy[0] - (yy[1] - yy[0])] + yy + [yy[-1] - (yy[-2] - yy[-1])]

        # Draw grid
        ctx.strokeStyle = '#eee'
        ctx.lineWidth = 1
        for y in range(0, h, 20):
            ctx.beginPath()
            ctx.moveTo(0, y)
            ctx.lineTo(w, y)
            ctx.stroke()
        for x in range(0, w, 20):
            ctx.beginPath()
            ctx.moveTo(x, 0)
            ctx.lineTo(x, h)
            ctx.stroke()

        # Draw nodes
        ctx.fillStyle = '#acf'
        ctx.strokeStyle = '#000'
        ctx.lineWidth = 2
        for i in range(1, len(xx) - 1):
            ctx.beginPath()
            ctx.arc(xx[i], yy[i], 9, 0, 6.2831)
            ctx.fill()
            ctx.stroke()

        # Select interpolation function
        fun = self['factors_' + self.spline_type.lower()]
        if not fun:
            fun = lambda: (0, 1, 0, 0)

        # Draw lines

        for i in range(1, len(xx) - 2):

            ctx.lineCap = "round"
            ctx.lineWidth = 3
            ctx.strokeStyle = '#008'
            support = 1 if self.spline_type == 'LINEAR' else 2
            if self._current_node is not None:
                if i - (support + 1) < self._current_node < i + support:
                    ctx.strokeStyle = '#08F'
                    ctx.lineWidth = 5

            # Get coordinates of the four points
            x0, y0 = xx[i - 1], yy[i - 1]
            x1, y1 = xx[i + 0], yy[i + 0]
            x2, y2 = xx[i + 1], yy[i + 1]
            x3, y3 = xx[i + 2], yy[i + 2]

            # Interpolate
            ctx.beginPath()
            # lineto = ctx.moveTo.bind(ctx)
            lineto = ctx.lineTo.bind(ctx)
            n = 30
            for t in [i / n for i in range(n + 1)]:
                f0, f1, f2, f3 = fun(t)
                x = x0 * f0 + x1 * f1 + x2 * f2 + x3 * f3
                y = y0 * f0 + y1 * f1 + y2 * f2 + y3 * f3

                lineto(x, y)
                lineto = ctx.lineTo.bind(ctx)

            ctx.stroke()