One place for hosting & domains

      How to Install and Secure the Mosquitto MQTT Messaging Broker on Debian 10


      Introduction

      MQTT is a machine-to-machine messaging protocol, designed to provide lightweight publish/subscribe communication to “Internet of Things” devices. It is commonly used for geo-tracking fleets of vehicles, home automation, environmental sensor networks, and utility-scale data collection.

      Mosquitto is a popular MQTT server (or broker, in MQTT parlance) that has great community support and is easy to install and configure.

      In this tutorial, we’ll install Mosquitto and set up our broker to use SSL to secure our password-protected MQTT communications.

      Prerequisites

      Before starting this tutorial, you will need:

      Step 1 — Installing Mosquitto

      Debian 10 has a fairly recent version of Mosquitto in its default software repository, so we can install it from there.

      First, log in using your non-root user and update the package lists using apt update:

      Now, install Mosquitto using apt install:

      • sudo apt install mosquitto mosquitto-clients

      By default, Debian will start the Mosquitto service after install. Let’s test the default configuration. We’ll use one of the Mosquitto clients we just installed to subscribe to a topic on our broker.

      Topics are labels that you publish messages to and subscribe to. They are arranged as a hierarchy, so you could have sensors/outside/temp and sensors/outside/humidity, for example. How you arrange topics is up to you and your needs. Throughout this tutorial we will use a simple test topic to test our configuration changes.

      Log in to your server a second time, so you have two terminals side-by-side. In the new terminal, use mosquitto_sub to subscribe to the test topic:

      • mosquitto_sub -h localhost -t test

      -h is used to specify the hostname of the MQTT server, and -t is the topic name. You’ll see no output after hitting ENTER because mosquitto_sub is waiting for messages to arrive. Switch back to your other terminal and publish a message:

      • mosquitto_pub -h localhost -t test -m "hello world"

      The options for mosquitto_pub are the same as mosquitto_sub, though this time we use the additional -m option to specify our message. Hit ENTER, and you should see hello world pop up in the other terminal. You’ve sent your first MQTT message!

      Enter CTRL+C in the second terminal to exit out of mosquitto_sub, but keep the connection to the server open. We’ll use it again for another test in Step 5.

      Next, we’ll secure our installation using password-based authentication.

      Step 2 — Configuring MQTT Passwords

      Let’s configure Mosquitto to use passwords. Mosquitto includes a utility to generate a special password file called mosquitto_passwd. This command will prompt you to enter a password for the specified username, and place the results in /etc/mosquitto/passwd.

      • sudo mosquitto_passwd -c /etc/mosquitto/passwd sammy

      Now we’ll open up a new configuration file for Mosquitto and tell it to use this password file to require logins for all connections:

      • sudo nano /etc/mosquitto/conf.d/default.conf

      This should open an empty file. Paste in the following:

      /etc/mosquitto/conf.d/default.conf

      allow_anonymous false
      password_file /etc/mosquitto/passwd
      
      

      Be sure to leave a trailing newline at the end of the file.

      allow_anonymous false will disable all non-authenticated connections, and the password_file line tells Mosquitto where to look for user and password information. Save and exit the file.

      Now we need to restart Mosquitto and test our changes.

      • sudo systemctl restart mosquitto

      Try to publish a message without a password:

      • mosquitto_pub -h localhost -t "test" -m "hello world"

      The message should be rejected:

      Output

      Connection Refused: not authorised. Error: The connection was refused.

      Before we try again with the password, switch to your second terminal window again, and subscribe to the ‘test’ topic, using the username and password this time:

      • mosquitto_sub -h localhost -t test -u "sammy" -P "password"

      It should connect and sit, waiting for messages. You can leave this terminal open and connected for the rest of the tutorial, as we’ll periodically send it test messages.

      Now publish a message with your other terminal, again using the username and password:

      • mosquitto_pub -h localhost -t "test" -m "hello world" -u "sammy" -P "password"

      The message should go through as in Step 1. We’ve successfully added password protection to Mosquitto. Unfortunately, we’re sending passwords unencrypted over the internet. We’ll fix that next by adding SSL encryption to Mosquitto.

      Step 3 — Configuring MQTT SSL

      To enable SSL encryption, we need to tell Mosquitto where our Let’s Encrypt certificates are stored. Open up the configuration file we previously started:

      • sudo nano /etc/mosquitto/conf.d/default.conf

      Paste in the following at the end of the file, leaving the two lines we already added:

      /etc/mosquitto/conf.d/default.conf

      . . .
      listener 1883 localhost
      
      listener 8883
      certfile /etc/letsencrypt/live/mqtt.example.com/cert.pem
      cafile /etc/letsencrypt/live/mqtt.example.com/chain.pem
      keyfile /etc/letsencrypt/live/mqtt.example.com/privkey.pem
      
      

      Again, be sure to leave a trailing newline at the end of the file.

      We’re adding two separate listener blocks to the config. The first, listener 1883 localhost, updates the default MQTT listener on port 1883, which is what we’ve been connecting to so far. 1883 is the standard unencrypted MQTT port. The localhost portion of the line instructs Mosquitto to only bind this port to the localhost interface, so it’s not accessible externally. External requests would have been blocked by our firewall anyway, but it’s good to be explicit.

      listener 8883 sets up an encrypted listener on port 8883. This is the standard port for MQTT + SSL, often referred to as MQTTS. The next three lines, certfile, cafile, and keyfile, all point Mosquitto to the appropriate Let’s Encrypt files to set up the encrypted connections.

      Save and exit the file, then restart Mosquitto to update the settings:

      • sudo systemctl restart mosquitto

      Update the firewall to allow connections to port 8883.

      Output

      Rule added Rule added (v6)

      Now we test again using mosquitto_pub, with a few different options for SSL:

      • mosquitto_pub -h mqtt.example.com -t test -m "hello again" -p 8883 --capath /etc/ssl/certs/ -u "sammy" -P "password"

      Note that we’re using the full hostname instead of localhost. Because our SSL certificate is issued for mqtt.example.com, if we attempt a secure connection to localhost we’ll get an error saying the hostname does not match the certificate hostname (even though they both point to the same Mosquitto server).

      --capath /etc/ssl/certs/ enables SSL for mosquitto_pub, and tells it where to look for root certificates. These are typically installed by your operating system, so the path is different for Mac OS, Windows, etc. mosquitto_pub uses the root certificate to verify that the Mosquitto server’s certificate was properly signed by the Let’s Encrypt certificate authority. It’s important to note that mosquitto_pub and mosquitto_sub will not attempt an SSL connection without this option (or the similar --cafile option), even if you’re connecting to the standard secure port of 8883.

      If all goes well with the test, we’ll see hello again show up in the other mosquitto_sub terminal. This means your server is fully set up! If you’d like to extend the MQTT protocol to work with websockets, you can follow the final step.

      Step 4 — Configuring MQTT Over Websockets (Optional)

      In order to speak MQTT using JavaScript from within web browsers, the protocol was adapted to work over standard websockets. If you don’t need this functionality, you may skip this step.

      We need to add one more listener block to our Mosquitto config:

      • sudo nano /etc/mosquitto/conf.d/default.conf

      At the end of the file, add the following:

      /etc/mosquitto/conf.d/default.conf

      . . .
      listener 8083
      protocol websockets
      certfile /etc/letsencrypt/live/mqtt.example.com/cert.pem
      cafile /etc/letsencrypt/live/mqtt.example.com/chain.pem
      keyfile /etc/letsencrypt/live/mqtt.example.com/privkey.pem
      
      

      Again, be sure to leave a trailing newline at the end of the file.

      This is mostly the same as the previous block, except for the port number and the protocol websockets line. There is no official standardized port for MQTT over websockets, but 8083 is the most common.

      Save and exit the file, then restart Mosquitto.

      • sudo systemctl restart mosquitto

      Now, open up port 8083 in the firewall.

      To test this functionality, we’ll use a public, browser-based MQTT client. There are a few out there, but the Eclipse Paho JavaScript Client is simple and straightforward to use. Open the Paho client in your browser. You’ll see the following:

      Paho Client Screen

      Fill out the connection information as follows:

      • Host should be the domain for your Mosquitto server, mqtt.example.com.
      • Port should be 8083.
      • ClientId can be left to the default value, js-utility-DI1m6.
      • Path can be left to the default value, /mqtt.
      • Username should be your Mosquitto username; here, we used sammy.
      • Password should be the password you chose.

      The remaining fields can be left to their default values.

      After pressing Connect, the Paho browser-based client will connect to your Mosquitto server.

      To publish a message, navigate to the Publish Message pane, fill out Topic as test, and enter any message in the Message section. Next, press Publish. The message will show up in your mosquitto_sub terminal.

      Conclusion

      We’ve now set up a secure, password-protected and SSL-secured MQTT server. This can serve as a robust and secure messaging platform for whatever projects you dream up. Some popular software and hardware that work well with the MQTT protocol include:

      • OwnTracks, an open-source geo-tracking app you can install on your phone. OwnTracks will periodically report position information to your MQTT server, which you could then store and display on a map, or create alerts and activate IoT hardware based on your location.
      • Node-RED is a browser-based graphical interface for 'wiring’ together the Internet of Things. You drag the output of one node to the input of another, and can route information through filters, between various protocols, into databases, and so on. MQTT is very well supported by Node-RED.
      • The ESP32 is an inexpensive wifi microcontroller with MQTT capabilities. You could wire one up to publish temperature data to a topic, or perhaps subscribe to a barometric pressure topic and sound a buzzer when a storm is coming!

      These are just a few popular examples from the MQTT ecosystem. There is much more hardware and software out there that speaks the protocol. If you already have a favorite hardware platform, or software language, it probably has MQTT capabilities. Have fun getting your “things” talking to each other!



      Source link

      An Overview of Open Source Data Visualization Tools


      Updated by Linode Contributed by Mihalis Tsoukalos

      Creating graphic visualizations for a data set is a powerful way to derive meaning from vast amounts of information. It provides a way to extract meaningful relationships between different aspects of your data depending on how the data is mapped and which graphic representations are chosen. Data visualization is a common practice in many sectors, including various scientific disciplines, business settings, the government sector, and education.

      There are many open source tools available to create sophisticated data visualizations for complex data sets. This guide will provide an introductory exploration of data analysis and 2D graphics rendering packages that can be used with R, Python, and JavaScript to generate data visualizations.

      In this guide you will complete the following steps:

      Before You Begin

      Ensure you have the following programs and packages installed on your computer:

      1. The R programming language
      2. The RStudio Desktop application
      3. Python 3.7.0 or higher
        1. pandas Python package
        2. Matplotlib Python package
        3. wordcloud Python package
      4. The Golang programming language

      Assumptions

      This guide assumes you have some basic familiarity with the following concepts and skills:

      1. Basic programming principles and data structures
      2. Are able to read code written in Go, Python, HTML, CSS, and JavaScript

      Create Your Data Sets

      In this section, you will create a data set using the contents of your Bash history file and optionally, your Zsh history file. You will then create a third data set using a Perl script that will extract information from the first two data sets. In the Create Visualizations for your Data section of the guide, you will use these various data sets to create corresponding visualizations.

      Data Set 1 – Bash History File

      A Bash history file stores all commands executed in your command line interpreter. View your 10 most recently executed commands with the following command:

      head ~/.bash_history
      

      Your output should resemble the following:

        
      git commit -a -m "Fixed Constants links"
      git push
      git diff
      docker images
      brew update; brew upgrade; brew cleanup
      git commit -a -m "Cleanup v2 and v3"
      git push
      git commit -a -m "Added examples section"
      git push
      cat ~/.lenses/lenses-cli.yml
      
      

      Create a new directory named data-setsto store your data and copy your Bash history file to the directory:

      mkdir data-sets && cp ~/.bash_history data-sets/data-1
      

      Data Set 2 – Zsh History File

      If you are using the Zsh shell interpreter, you can use its history file as a second data set. Zsh’s history file format includes data that you will need to exclude from your data set. Use AWK to clean up your Zsh history file and save the output to a new file in the data-sets directory:

      awk -F ";" '{$1=""; print $0}' ~/.zsh_history | sed -e "s/^[ t]*//" -e "/^$/d" > data-sets/data-2
      

      Data Set 3 – Perl Script

      To create your third data set, you will use a Perl script that categorizes the contents of your data set files. The categorization is based on the word count for each line of text in your data files or, in other words, the length of each command stored in your Bash and Zsh history files. The script creates 5 categories of command lengths; 1 – 2 words, 3 – 5 words, 6 – 10 words, 11 – 15 words, and 16 or more words.

      1. Create a file named command_length.pl in your home directory with the following content:

        ~/command_length.pl
         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
        
        #!/usr/bin/perl -w
        
        use strict;
        
        my $directory = "";
        my $filename = "";
        
        my $CAT1 = 0;
        my $CAT2 = 0;
        my $CAT3 = 0;
        my $CAT4 = 0;
        my $CAT5 = 0;
        
        if ( @ARGV != 1 ) {
           die <<Thanatos
              usage info:
                 Please use exactly 1 argument!
        Thanatos
        }
        
        ($directory) = @ARGV;
        opendir(BIN, $directory)
            || die "Error opening directory $directory: $!n";
        
        while (defined ($filename = readdir BIN) ) {
            # The following command does not process . and ..
            next if( $filename =~ /^..?$/ );
            process_file($directory."/".$filename);
        }
        
        print "Category1tt$CAT1n";
        print "Category2tt$CAT2n";
        print "Category3tt$CAT3n";
        print "Category4tt$CAT4n";
        print "Category5tt$CAT5n";
        exit 0;
        
        sub process_file {
            my $file = shift;
            my $line = "";
        
            open (HISTORYFILE, "< $file")
                || die "Cannot open $file: $!n";
        
            while (defined($line = <HISTORYFILE>)) {
                chomp $line;
                next if ( ! defined($line) );
                check_category($line);
            }
        }
        
        sub check_category {
            my $command = shift;
            chomp $command;
            my $length = length($command);
        
            if ( $length <= 2 ) { $CAT1 ++; }
            elsif ( $length <= 5 ) { $CAT2 ++; }
            elsif ( $length <= 10 ) { $CAT3 ++; }
            elsif ( $length <= 15 ) { $CAT4 ++; }
            else { $CAT5 ++; }
        }
            
      2. Run the Perl script. The script expects a single argument; the directory that holds all data files that you want to process. The file’s output will be saved in a new file command_categories.txt. The command_categories.txt file will be used later in this guide to create visualizations using R.

        ./command_length.pl data-sets > ~/command_categories.txt
        

        Note

        Your Perl script must be executable in order to run. To add these permissions, execute the following command:

        chmod +x command_length.pl
        

        Open the .command_categories.txt file to view the categorizations created by your Perl script. Your file should resemble the following example:

        command_categories.txt
        1
        2
        3
        4
        5
        6
        7
        
        "Category Name" "Number of Times"
        Category1 5514
        Category2 2381
        Category3 2624
        Category4 2021
        Category5 11055
            

      You now have three sources of data that you can use to explore data visualization tools in the next sections.

      • ~/data-sets/data-1
      • ~/data-stes/data-2
      • ~/command_categories.txt

      Create Visualizations for your Data

      In this section you will use the data sets you created in the previous section to generate visualizations for them.

      Visualize your Data with R and RStudio

      R is a specialized programming language used for statistical computing and graphics. It is especially good for creating high quality graphs, like density plots, line charts, pie charts, and scatter plots. RStudio is an integrated development environment (IDE) for R that includes debugging and plotting tools that make it easy to write, debug, and run R scripts.

      In this section, you will use the command_categories.txt file created in the Data Set 3 – Perl Script section and RStudio to create a pie chart and simple spreadsheet of your data.

      1. Open RStudio Desktop and create a data frame. In R, a data frame is a table similar to a two-dimensional array.

        DATA <- read.table("~/command_categories.txt", header=TRUE)
        
        • This command will read the command_categories.txt file that was created in the Data Set 3 – Perl Script section of the guide and create a data frame from it that is stored in the DATA variable.

        • The header=TRUE argument indicates that the file’s first row contains variable names for column values. This means that Category Name and Number of Times will be used as variable names for the two columns of values in command_categories.txt.

      2. Next, create a pie chart visualization for each column of values using R’s pie() function.

        pie(DATA$Number.of.Times, DATA$Category.Name)
        
        • The function’s first argument, DATA$Number.of.Times and DATA$Category.Name, provides the x-vector numeric values to use when creating the pie chart visualization.

        • The second argument, DATA$Category.Name, provides the labels for each pie slice.

        RStudio will display a pie chart visualization of your data in the Plots and Files window similar to the following:

        Pie Chart

      3. Visualize your data in a spreadsheet style format using R’s View() function.

        View(DATA, "Command Lengths")
        

        RStudio will display a spreadsheet style viewer in a new window pane tab.

        Spreadsheet

      Explore R’s graphics package to discover additional functions you can use to create more complex visualizations for your data with RStudio.

      Create a Word Cloud using Python

      Word clouds depict text data using varying font sizes and colors to visually demonstrate the frequency and relative importance of each word. A common application for word clouds is to visualize tag or keyword relevancy. In this section you will use Python3 and your Bash and Zsh history files to generate a word cloud of all your shell commands. The Python packages listed below are commonly used in programs for data analysis and scientific computing and you will use them to generate your word cloud.

      1. Create a file named create_wordcloud.py in your home directory with the following content:

        ~/create_wordcloud.py
         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
        
        #!/usr/bin/env python3
        
        import pandas as pd
        import matplotlib.pyplot as plt
        import sys
        import os
        from wordcloud import WordCloud, STOPWORDS
        from random import randrange
        
        path = sys.argv[1]
        
        if os.path.exists(path):
            print("Creating word cloud for file: " + path)
        
            data = pd.read_table(path, header = None, names=["CMD"], encoding = "iso-8859-1")
        
            skipWords = []
            skipWords.extend(STOPWORDS)
        
            words = ''.join(data['CMD'])
        
            w = WordCloud(
                    stopwords=set(skipWords),
                    background_color='gray',
                    width=4000,
                    height=2000
                    ).generate(words)
            filename = "word_cloud_" + str(randrange(100)) + ".png"
            print("Your wordcloud's filename is: " + filename)
        
            plt.imshow(w)
            plt.axis("off")
            plt.savefig(filename, dpi=1000)
        
        else:
            print("File" + path +  "does not exist")
            
      2. Run your Python script and pass the path of one of your data set files as an argument. The script will read the contents of the file using panda’s read_table() function and convert it into a data frame with a column name of CMD. It will then use the data in the CMD column to create a concatenated string representation of the data that can be passed to wordcloud to generate a .png wordcloud image.

        ./create_wordcloud.py ~/data-sets/data-1
        

        You should see a similar output from the Python script:

          
        Creating word cloud for file: /Users/username/data-sets/data-2
        Your word cloud's filename is: word_cloud_58.png
            
        
      3. Open the word_cloud_58.png image file to view your word cloud.

        Word Cloud

      You could use a similar process to create a word cloud visualization for any text you want to analyze.

      Visualize Data using D3.js

      D3.js is a JavaScript library that helps you visualize JSON formatted data using HTML, SVG, and CSS. In this section you will us D3.js to create and embed a pie chart visualization into a web page.

      To convert your data set into JSON, you will create a Golang command line utility that generates JSON formatted plain text output. For more complex data sets, you might consider creating a similar command line utility using Golang’s json package.

      1. Create a file named cToJSON.go in your home directory with the following content:

        ./cToJSON.go
         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
        
        package main
        
        import (
            "bufio"
            "flag"
            "fmt"
            "io"
            "os"
            "regexp"
            "sort"
        )
        
        var DATA = make(map[string]int)
        
        func lineByLine(file string) error {
            var err error
            f, err := os.Open(file)
            if err != nil {
                return err
            }
            defer f.Close()
        
            r := bufio.NewReader(f)
            for {
                line, err := r.ReadString('n')
                if err == io.EOF {
                    break
                } else if err != nil {
                    fmt.Printf("error reading file %s", err)
                    break
                }
        
                r := regexp.MustCompile("[^\s]+")
                words := r.FindAllString(line, -1)
                if len(words) == 0 {
                    continue
                }
        
                if _, ok := DATA[words[0]]; ok {
                    DATA[words[0]]++
                } else {
                    DATA[words[0]] = 1
                }
        
            }
            return nil
        }
        
        func main() {
            flag.Parse()
            if len(flag.Args()) == 0 {
                fmt.Printf("usage: cToJSON <file1> [<file2> ...]n")
                return
            }
        
            for _, file := range flag.Args() {
                err := lineByLine(file)
                if err != nil {
                    fmt.Println(err)
                }
            }
        
            n := map[int][]string{}
            var a []int
            for k, v := range DATA {
                n[v] = append(n[v], k)
            }
        
            for k := range n {
                a = append(a, k)
            }
        
            fmt.Println("[")
            sort.Sort(sort.Reverse(sort.IntSlice(a)))
        
            counter := 0
            for _, k := range a {
                if counter >= 10 {
                    break
                }
        
                for _, s := range n[k] {
                    if counter >= 10 {
                        break
                    }
                    counter++
                    fmt.Printf("{"command":"%s","count":%d},n", s, k)
                }
            }
            fmt.Println("];")
        }
            
        • The utility expects file paths to your Bash and Zsh data sets as arguments.
        • It will then read the files and find the 10 most popular commands and output it as JSON formatted data.
        • Several Golang standard library packages are used in the utility to perform operations liking reading files, using regular expressions, and sorting collections.
      2. Run the command line utility and pass in the paths to each command history data set:

        go run cToJSON.go data-set/data-1 data-set/data-2
        

        Your output should resemble the following:

          
        [
        {"command":"ll","count":1832},
        {"command":"git","count":1567},
        {"command":"cd","count":982},
        {"command":"brew","count":926},
        {"command":"unison","count":916},
        {"command":"gdf","count":478},
        {"command":"ssh","count":474},
        {"command":"rm","count":471},
        {"command":"sync","count":440},
        {"command":"ls","count":421},
        ];
            
        

        You are now ready to create your pie chart visualization and embed it into a web page using D3.js

      3. Create an HTML file named pieChart.html and copy and paste the following content. The DATA variable on line 31 contains the JSON data that was created by the cToJSON.go script in the previous step. Remove the JSON data in the example and replace it with your own JSON data.

        Note

        In this example, your JSON data is hardcoded in pieChart.html for simplicity. Web browser security constraints restrict how a document or script loaded from one origin can interact with a resource from another origin. However, you may consider using the d3-fetch module to fetch your JSON data from a specific URL.
        ~/pieChart.html
          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
        
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <meta charset="utf-8">
            <title>History Visualization</title>
        
            <style type="text/css">
              * { margin: 0; padding: 0; }
        
              #chart {
                background-color: white;
                font: 14px sans-serif;
                margin: 0 auto 50px;
                width: 600px;
                height: 600px;
              }
              #chart .label{
                fill: #404040;
                font-size: 12px;
              }
            </style>
          </head>
        
          <body>
            <div id="chart"></div>
          </body>
        
          <script src="https://d3js.org/d3.v3.min.js"></script>
          <script type="text/javascript">
        
          var DATA = [
              {"command":"ll","count":1832},
              {"command":"git","count":1567},
              {"command":"cd","count":982},
              {"command":"brew","count":926},
              {"command":"unison","count":916},
              {"command":"gdf","count":478},
              {"command":"ssh","count":474},
              {"command":"rm","count":471},
              {"command":"sync","count":440},
              {"command":"ls","count":421}
              ];
        
            var width  = 600;
                height = 600;
                radius = width / 2.5;
        
            var pie = d3.layout.pie()
                        .value(function(d) { return d.count; })
        
            var pieData = pie(DATA);
            var color = d3.scale.category20();
        
            var arc = d3.svg.arc()
                        .innerRadius(0)
                        .outerRadius(radius - 7);
        
            var svg = d3.select("#chart").append("svg")
                        .attr("width", width)
                        .attr("height", height)
                        .append("g")
                        .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
        
            var ticks = svg.selectAll("line")
                           .data(pieData)
                           .enter()
                           .append("line");
        
            ticks.attr("x1", 0)
                 .attr("x2", 0)
                 .attr("y1", -radius+4)
                 .attr("y2", -radius-2)
                 .attr("stroke", "black")
                 .attr("transform", function(d) {
                   return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
                 });
        
            var labels = svg.selectAll("text")
                            .data(pieData)
                            .enter()
                            .append("text");
        
            labels.attr("class", "label")
                  .attr("transform", function(d) {
                     var dist   = radius + 25;
                         angle  = (d.startAngle + d.endAngle) / 2;
                         x      = dist * Math.sin(angle);
                         y      = -dist * Math.cos(angle);
                     return "translate(" + x + "," + y + ")";
                   })
                  .attr("dy", "0.35em")
                  .attr("text-anchor", "middle")
                  .text(function(d){
                    return d.data.command + " (" + d.data.count + ")";
                  });
        
            var path = svg.selectAll("path")
                          .data(pieData)
                          .enter()
                          .append("path")
                          .attr("fill", function(d, i) { return color(i); })
                          .attr("d", arc);
          </script>
        </html>
        
      4. Navigate to your preferred browser and enter the HTML file’s absolute path to view the pie chart. For a macOS user that has stored the HTML file in their home directory, the path would resemble the following: /Users/username/pieChart.html

        JS Pie Chart

      Next Steps

      Now that you are familiar with some data visualization tools and simple techniques, you can begin to explore more sophisticated approaches using the same tools explored in this guide. Here are a few ideas you can consider:

      • Create a new data set by extracting all git related commands from your history files; analyze and visualize them.
      • Automate some of the techniques discussed in this guide using Cron jobs to generate your data sets automatically.
      • Explore the Python for Data Science eBook’s data visualization section for a deeper dive into using pandas.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      INAP Executive Spotlight: TJ Waldorf, CMO—Head of Inside Sales and Customer Success


      In the INAP Executive Spotlight series, we interview senior leaders across the organization, hearing candid reflections about their careers, the mentors who shaped them and big lessons learned along the way.TJ Waldorf

      Next in the series is TJ Waldorf, CMO and Head of Inside Sales and Customer Success. Prior to this role, he served as Vice President of Global Marketing at INAP and Vice President of Inside Sales and Marketing at SingleHop, which was acquired by INAP in 2018.

      In our conversation, Waldorf discussed what excites him about the INAP brand, how he got to where he is today after initially pursuing an early career in graphic design and the importance of mentorship. Read on to learn about these topics and more.

      The interview has been lightly edited for clarity and length.

      Tell us how you got into sales and marketing. What inspired you to pursue these areas of business?

      It’s funny when I think on this, because I distinctly remember telling myself that I’d never be a salesperson. Back when I was a teen, I viewed sales as the proverbial snake oil salesman tricking people into buying things they didn’t need. I originally aspired to be a graphic designer and earned a degree in design and visual communications. I always loved drawing and creating. I got that from my mom. But as I progressed into my early 20s and my first real job, I realized sales (and marketing) are about service. We are serving the needs of people and businesses. That was something I could really get behind.

      What excites you most about the INAP brand as it stands today?

      In November, we’ll celebrate the one-year anniversary of our refreshed brand identity and direction: Performance for Your Purpose. At the most basic level, we’re in the data center and cloud services space, yet what we’re doing is providing the foundation for our customers to deliver their services to their customers and deliver on their purpose and mission.

      If we’re not operating optimally, there’s a very distinct domino effect. Have you ever tried accessing a website or an application and found it was unavailable or moving very slowly? We all have. In some cases, that’s because the underlying infrastructure is not working properly, or there are issues at the application level. At INAP, we promise high performance, reliable service and an exceptional customer experience. When we deliver on these promises, our customers get to deliver on their promises. That’s what gets me fired up and excited about the INAP brand. The impact we have on the services that power aspects of our everyday lives is incredibly exciting.

      You recently became CMO and have Inside Sales and Customer Success under your wing, along with Marketing. What are some changes or challenges you’re seeing in these areas of the business?

      I feel very fortunate to have the opportunity to oversee these three teams and to view them through a singular lens of how we approach the end-to-end customer life cycle and experience. The addition of the Customer Success org makes logical sense given some of the similarities in the work they do relative to inside sales, and the significant marketing impact they have on overall customer experience. After all, the best marketing comes from word of mouth, so if we (marketing) can enable the customer success org to accelerate the chatter, we’re in a great spot.

      As far as challenges go—and this is not unique to INAP—we work in a very competitive space and must constantly prove our value to our customers. They have choices in the market, so it’s our job, collectively, to reinforce why they chose us to begin with and why it’s in their best interest to stay with us for the long term. It’s certainly not an easy job, but I think we have an opportunity keep improving on the great work these teams have done so far.

      Out of the qualities you possess, which do you think has had the greatest influence on your success? 

      Without a doubt, the first is my drive for lifelong learning. I’ve never operated in any role where I thought I knew everything there is to know, and I enjoy the process of learning and growing my knowledge about a topic. I’m never afraid to ask the potentially dumb question, because nine times out of ten, lots of others in the room have the same question.

      The other quality is finding great people to surround myself with, be it people I report to, people who report to me or mentors I’ve had over the years. There’s a saying that goes, “If you want to go fast, go alone. If you want to go far, to together.” I think about my career in that way. I have a great team here at INAP and see the momentum we’re building together.

      Who are the people that have mentored you or been role models? 

      How much time do we have? I cannot stress the importance of having a mentor or multiple mentors. You can learn things so much faster than without them. This has been critical for me, and I don’t think I’d be where I am today without these very important people in my life.

      My parents are truly are the foundation of who I am today. I’m trying to pass the values they shared down to my son. I’ve also had many great mentors throughout my career and find myself bringing new ones into the mix when new challenges or opportunities pop up. I have mentors that run the gamut from CEOs to CMOs, VCs to what usually gets referred to as ‘reverse mentors’—folks younger than me that can keep me plugged into what’s important for the next generation. I even find myself learning from my nine-year-old. Maybe he’s a mini-mentor.

      What advice would you give to someone pursuing sales or marketing in tech, specifically? 

      Remember that your job is to be in service of your customers and their objectives. This is something I learned from my dad. You’re helping them make educated decisions on how the services, tools or platforms you provide will best help them achieve their goals. For sales and marketing, especially in tech, it’s far too easy to get bogged down in features and functionality and forget why a solution was built to begin with. Stay focused on the problem you’re helping the customer solve and you’ll be miles ahead of your peers.

      What are some of the big lessons you’ve learned in your career?

      Being exceptional at hiring and retaining great people probably tops my list. When I first started as a manager, I thought I had to have all the answers and tell people exactly what to do. But I learned that hiring great people and enabling them to do what they do best makes work, and life, 10x more productive and easier. This lesson came the hard way through lots of trial and error. This points back to the old adage of work smarter not harder.

      What are your thoughts on work-life balance? Have your ideas changed over time?

      I once heard Amazon’s CEO, Jeff Bezos, refer to this as “work-life-harmony.” That stuck with me. It’s about harmonizing the work and life to achieve your personal objectives in both areas. I do think, however, that there is a time and place to completely unplug. I ebb and flow in this area. My wife and I are both working parents and we try to make sure we’re helping one another find that harmony. Work is such a large part of our life but it’s good to keep its purpose in perspective.

      Laura Vietmeyer


      READ MORE



      Source link