Example #1
0
 def get_default_thumbnail_encoding(self):
     try:
         filepath = os.path.join(settings.STATIC_ROOT, 'img',
                                 'kolibri_placeholder.png')
         return encode_file_to_base64(filepath, "data:image/png;base64,")
     except IOError:
         logging.warning("Could not find {}".format(filepath))
    def get_tagcloud(self, tags, tag_limit=None):
        tag_limit = tag_limit or len(tags)
        tags = sorted(tags, key=lambda kv: -kv['count'])[:tag_limit]  # Get top X tags
        tag_dict = {t['tag_name']: t['count'] for t in tags}

        # Generate a word cloud image
        wordcloud = WordCloud(
            background_color='white',
            min_font_size=10,
            max_font_size=60,
            width=self.tagcloud_width,
            height=self.tagcloud_height or 30 * len(tags) / 2 + 10,
            font_path=os.path.sep.join([settings.STATIC_ROOT, 'fonts', 'OpenSans-Regular.ttf'])
        ).generate_from_frequencies(tag_dict)

        tag_counts = [t['count'] for t in tags]
        step = (float(max(tag_counts))) / len(self.color_selection)
        thresholds = list(reversed([int(round(i * step)) for i in range(len(self.color_selection))]))

        def get_color(word, font_size, position, orientation, random_state=None, **kwargs):
            index = next((i for i, t in enumerate(thresholds) if tag_dict[word] >= t), 0)
            return self.color_selection[index]

        wordcloud.recolor(color_func=get_color)
        image = wordcloud.to_image()
        filepath = self.get_write_to_path(ext="png")
        image.save(filepath)
        return encode_file_to_base64(filepath, "data:image/png;base64,")
Example #3
0
    def get_tagcloud(self, tags, tag_limit=None):
        tag_limit = tag_limit or len(tags)
        tags = sorted(
            tags, key=lambda kv: -kv['count'])[:tag_limit]  # Get top X tags
        tag_dict = {t['tag_name']: t['count'] for t in tags}

        # Generate a word cloud image
        wordcloud = WordCloud(background_color='white',
                              min_font_size=10,
                              max_font_size=60,
                              width=self.tagcloud_width,
                              height=self.tagcloud_height
                              or 30 * len(tags) / 2 + 10,
                              font_path=os.path.sep.join([
                                  settings.STATIC_ROOT, 'fonts',
                                  'OpenSans-Regular.ttf'
                              ])).generate_from_frequencies(tag_dict)

        tag_counts = [t['count'] for t in tags]
        step = (float(max(tag_counts))) / len(self.color_selection)
        thresholds = list(
            reversed([
                int(round(i * step)) for i in range(len(self.color_selection))
            ]))

        def get_color(word,
                      font_size,
                      position,
                      orientation,
                      random_state=None,
                      **kwargs):
            index = next(
                (i for i, t in enumerate(thresholds) if tag_dict[word] >= t),
                0)
            return self.color_selection[index]

        wordcloud.recolor(color_func=get_color)
        image = wordcloud.to_image()
        filepath = self.get_write_to_path(ext="png")
        image.save(filepath)
        return encode_file_to_base64(filepath, "data:image/png;base64,")
Example #4
0
    def write_slide(self, channel):
        self.get_next_slide()
        data = self.get_channel_data(channel)

        next_line = 0.1  # Keeps track of last line (useful for setting the top location of shapes)

        # Add thumbnail
        padding = 0.2
        thumbnail_width = 1.1
        if data['thumbnail']:
            thumbnail = self.add_picture(data['thumbnail'], padding, padding,
                                         thumbnail_width, thumbnail_width)
            thumbnail.line.color.rgb = self.gray
            thumbnail.line.width = Inches(0.01)

        # Add title/description
        title_left = thumbnail_width + padding * 2

        title_height = 0.5
        title_tf = self.generate_textbox(title_left, next_line,
                                         self.width - title_left, title_height)
        title_tf.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
        self.add_line(title_tf,
                      channel.name,
                      fontsize=24,
                      bold=True,
                      append=False)
        next_line += title_height

        # Variables for section under title
        includes_width = 2
        size_height = 0.5
        description_height = 1.25
        size_width = self.width - title_left - includes_width - padding * 2

        # Add language information
        icon_width = 0.2
        language_left = size_width + title_left + padding
        language_icon_path = os.path.join(settings.STATIC_ROOT, 'img',
                                          'export', 'language.png')
        encoding = encode_file_to_base64(language_icon_path,
                                         'data:image/png;base64,')
        self.add_picture(encoding, language_left, next_line + 0.04, icon_width,
                         icon_width)

        includes_tf = self.generate_textbox(language_left + icon_width - 0.08,
                                            next_line, includes_width,
                                            size_height + description_height)
        language = channel.language.native_name if channel.language else _(
            "No language set")
        self.add_line(includes_tf,
                      " {}".format(language),
                      append=False,
                      bold=True)
        if data['accessible_languages']:
            self.add_line(includes_tf,
                          _("    * Subtitles included"),
                          fontsize=10,
                          space_before=2)

        # Add For Educators: Coach Content
        if data['includes'].get('coach_content'):
            coach_content = self.add_line(
                includes_tf,
                "✔",
                bold=True,
                color=self.get_rgb_from_hex(EXERCISE_COLOR),
                space_before=4)
            self.add_run(coach_content, _(" Coach Content"))

        # Add For Educators: Assessments
        if data['includes'].get('exercises'):
            assessments = self.add_line(
                includes_tf,
                "✔",
                bold=True,
                color=self.get_rgb_from_hex(EXERCISE_COLOR),
                space_before=4)
            self.add_run(assessments, _(" Assessments"))

        # Add size information
        size_tf = self.generate_textbox(title_left, next_line, size_width,
                                        size_height)
        size_bar = self.add_line(size_tf, "", append=False)
        for i in data['size']['scale']:
            self.add_run(size_bar,
                         "▮",
                         color=self.get_rgb_from_hex(EXERCISE_COLOR)
                         if i < data['size']['filled'] else self.gray,
                         fontsize=14)
        self.add_line(size_tf,
                      _("Channel size: %(size)s") %
                      {"size": data['size']['text'].lower()},
                      fontsize=8,
                      italic=True,
                      color=self.gray)
        next_line += size_height

        # Add description
        description_tf = self.generate_textbox(title_left, next_line,
                                               size_width, description_height)
        self.add_line(description_tf,
                      channel.description,
                      color=self.gray,
                      append=False)
        description_tf.fit_text()
        next_line += description_height + 0.1

        # Add separator with headers
        separator_height = 0.3
        self.add_shape(left=0,
                       top=next_line,
                       width=self.width / 2,
                       height=separator_height,
                       color=self.get_rgb_from_hex(EXERCISE_COLOR))
        resource_header = self.generate_textbox(padding, next_line,
                                                self.width / 2 - padding,
                                                separator_height)
        self.add_line(resource_header,
                      _("Resource Breakdown"),
                      bold=True,
                      color=self.get_rgb_from_hex("#FFFFFF"),
                      append=False)

        self.add_shape(left=self.width / 2,
                       top=next_line,
                       width=self.width / 2,
                       height=separator_height,
                       color=self.get_rgb_from_hex("#595959"))
        tag_header = self.generate_textbox(padding + self.width / 2 - padding,
                                           next_line, self.width / 2 - padding,
                                           separator_height)
        self.add_line(tag_header,
                      _("Most Common Tags"),
                      bold=True,
                      color=self.get_rgb_from_hex("#FFFFFF"),
                      append=False)
        next_line += separator_height + 0.05

        # Add piechart
        chart_height = 2.3
        if data['resource_count']:
            self.add_picture(data['piechart'],
                             0,
                             next_line,
                             self.width / 2 - 1,
                             height=chart_height)
        else:
            empty_tf = self.generate_textbox(0, next_line, self.width / 2,
                                             chart_height)
            empty_line = self.add_line(empty_tf,
                                       _("No Resources Found"),
                                       color=self.gray,
                                       fontsize=14,
                                       italic=True)
            empty_line.alignment = PP_ALIGN.CENTER

        # Add tagcloud
        if data['tags']:
            self.add_picture(data['tagcloud'], self.width / 2 + padding,
                             next_line + 0.1, self.width / 2 - 1,
                             chart_height - padding * 2)
        else:
            empty_tf = self.generate_textbox(self.width / 2, next_line,
                                             self.width / 2, chart_height)
            empty_line = self.add_line(empty_tf,
                                       _("No Tags Found"),
                                       color=self.gray,
                                       fontsize=14,
                                       italic=True)
            empty_line.alignment = PP_ALIGN.CENTER
        next_line += chart_height + 0.01

        # Add logo
        logo_width = 0.9
        logo_height = 0.25
        logo_left = Inches(self.width / 2 - logo_width / 2)
        try:
            logo_url = os.path.join(settings.STATIC_ROOT, 'img',
                                    'le_login.png')
            self.slide.shapes.add_picture(logo_url,
                                          logo_left,
                                          Inches(next_line),
                                          width=Inches(logo_width),
                                          height=Inches(logo_height))
        except IOError:
            logging.warning("Unable to add LE logo")
        next_line += logo_height

        # Add disclaimer
        disclaimer_tf = self.generate_textbox(0, next_line, self.width, 0.2)
        disclaimer_line = self.add_line(
            disclaimer_tf,
            _("This slide was automatically generated by Kolibri Studio, a product of Learning Equality"
              ),
            fontsize=7,
            color=self.gray,
            append=False)
        disclaimer_line.alignment = PP_ALIGN.CENTER
Example #5
0
    def get_pie_chart(self, counts, small_layout=False):
        # Put kind counts in a usable format
        kinds = list(
            ContentKind.objects.exclude(
                kind=content_kinds.TOPIC).order_by('kind').values_list(
                    'kind', flat=True))
        kind_vals = {
            k: next((c['count'] for c in counts if c['kind_id'] == k), 0)
            for k in kinds
        }
        kind_vals = OrderedDict(sorted(kind_vals.items()))
        sizes = [v for k, v in kind_vals.items()]
        total = max(sum(sizes), 1)

        labels = [{
            "text":
            ' {text} \n{p:.1f}%'.format(text=self.pluralize_constant(v, k),
                                        p=float(v) / total * 100.0),
            "count":
            v
        } for k, v in kind_vals.items()]

        # Create pie chart
        fig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
        wedgeprops = {
            "edgecolor": "white",
            'linewidth': 1,
            'linestyle': 'solid',
            'antialiased': True
        }
        wedges, texts = ax.pie(sizes,
                               colors=self.color_selection,
                               wedgeprops=wedgeprops)

        bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
        kw = dict(xycoords='data',
                  textcoords='data',
                  arrowprops=dict(arrowstyle="-"),
                  bbox=bbox_props,
                  zorder=0,
                  va="center")

        # Add popout labels for the larger layout
        if not small_layout:
            for i, p in enumerate(wedges):
                if not labels[i]['count']:
                    continue

                ang = (p.theta2 - p.theta1) / 2. + p.theta1
                y = np.sin(np.deg2rad(ang))
                x = np.cos(np.deg2rad(ang))
                connectionstyle = "angle,angleA=0,angleB={}".format(ang)
                kw["arrowprops"].update({
                    "connectionstyle": connectionstyle,
                    "facecolor": "gray"
                })
                ax.annotate(labels[i]['text'],
                            xy=(x, y),
                            xytext=(1.35 * np.sign(x), 1.4 * y),
                            ha="center",
                            fontsize=10,
                            **kw)

        # Add legend for the smaller layout
        else:
            plt.legend(loc='center left',
                       labels=[l['text'].split('\n')[0] for l in labels],
                       prop={'size': 20},
                       bbox_to_anchor=(0.7, 0.5),
                       bbox_transform=plt.gcf().transFigure)

        # Set up size variables for center circle
        center_text_size = 25 if small_layout else 20  # Renders smaller, so text needs to be bigger
        center_text_ratio = 0.75 if small_layout else 0.6

        # Add center circle
        circle = plt.Circle((0, 0), center_text_ratio, fc='white')
        centertext = self.pluralize_constant(sum(sizes),
                                             "resource_split").split("\n")
        plt.annotate(centertext[0],
                     xy=(0, 0.1),
                     fontsize=center_text_size,
                     ha="center")
        plt.annotate(centertext[1],
                     xy=(0, -0.15),
                     fontsize=center_text_size - 5,
                     ha="center")
        fig = plt.gcf()
        fig.gca().add_artist(circle)
        plt.tight_layout()

        # Write chart to image and get encoding
        filepath = self.get_write_to_path(ext="png")
        plt.savefig(filepath, bbox_inches='tight')
        plt.clf()
        plt.close()
        return encode_file_to_base64(filepath, "data:image/png;base64,")
    def write_slide(self, channel):
        self.get_next_slide()
        data = self.get_channel_data(channel)

        next_line = 0.1  # Keeps track of last line (useful for setting the top location of shapes)

        # Add thumbnail
        padding = 0.2
        thumbnail_width = 1.1
        if data['thumbnail']:
            thumbnail = self.add_picture(data['thumbnail'], padding, padding, thumbnail_width, thumbnail_width)
            thumbnail.line.color.rgb = self.gray
            thumbnail.line.width = Inches(0.01)

        # Add title/description
        title_left = thumbnail_width + padding * 2

        title_height = 0.5
        title_tf = self.generate_textbox(title_left,  next_line,  self.width - title_left, title_height)
        title_tf.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
        self.add_line(title_tf, channel.name, fontsize=24, bold=True, append=False)
        next_line += title_height

        # Variables for section under title
        includes_width = 2
        size_height = 0.5
        description_height = 1.25
        size_width = self.width - title_left - includes_width - padding * 2

        # Add language information
        icon_width = 0.2
        language_left = size_width + title_left + padding
        language_icon_path = os.path.join(settings.STATIC_ROOT, 'img', 'export', 'language.png')
        encoding = encode_file_to_base64(language_icon_path, 'data:image/png;base64,')
        self.add_picture(encoding, language_left, next_line + 0.04, icon_width, icon_width)

        includes_tf = self.generate_textbox(language_left + icon_width - 0.08,  next_line,  includes_width, size_height + description_height)
        language = channel.language.native_name if channel.language else _("No language set")
        self.add_line(includes_tf, " {}".format(language), append=False, bold=True)
        if data['accessible_languages']:
            self.add_line(includes_tf, _("    * Subtitles included"), fontsize=10, space_before=2)

        # Add For Educators: Coach Content
        if data['includes'].get('coach_content'):
            coach_content = self.add_line(includes_tf, "✔", bold=True, color=self.get_rgb_from_hex(EXERCISE_COLOR), space_before=4)
            self.add_run(coach_content, _(" Coach Content"))

        # Add For Educators: Assessments
        if data['includes'].get('exercises'):
            assessments = self.add_line(includes_tf, "✔", bold=True, color=self.get_rgb_from_hex(EXERCISE_COLOR), space_before=4)
            self.add_run(assessments, _(" Assessments"))

        # Add size information
        size_tf = self.generate_textbox(title_left,  next_line,  size_width, size_height)
        size_bar = self.add_line(size_tf, "", append=False)
        for i in data['size']['scale']:
            self.add_run(size_bar, "▮", color=self.get_rgb_from_hex(EXERCISE_COLOR) if i < data['size']['filled'] else self.gray, fontsize=14)
        self.add_line(size_tf, _("Channel size: %(size)s") % {"size": data['size']['text'].lower()}, fontsize=8, italic=True, color=self.gray)
        next_line += size_height

        # Add description
        description_tf = self.generate_textbox(title_left,  next_line, size_width, description_height)
        self.add_line(description_tf, channel.description, color=self.gray, append=False)
        description_tf.fit_text()
        next_line += description_height + 0.1

        # Add separator with headers
        separator_height = 0.3
        self.add_shape(left=0, top=next_line, width=self.width/2, height=separator_height, color=self.get_rgb_from_hex(EXERCISE_COLOR))
        resource_header = self.generate_textbox(padding, next_line, self.width / 2 - padding, separator_height)
        self.add_line(resource_header, _("Resource Breakdown"), bold=True, color=self.get_rgb_from_hex("#FFFFFF"), append=False)

        self.add_shape(left=self.width/2, top=next_line, width=self.width/2, height=separator_height, color=self.get_rgb_from_hex("#595959"))
        tag_header = self.generate_textbox(padding + self.width / 2 - padding, next_line, self.width / 2 - padding, separator_height)
        self.add_line(tag_header, _("Most Common Tags"), bold=True, color=self.get_rgb_from_hex("#FFFFFF"), append=False)
        next_line += separator_height + 0.05

        # Add piechart
        chart_height = 2.3
        if data['resource_count']:
            self.add_picture(data['piechart'], 0, next_line, self.width / 2 - 1, height=chart_height)
        else:
            empty_tf = self.generate_textbox(0,  next_line, self.width / 2, chart_height)
            empty_line = self.add_line(empty_tf, _("No Resources Found"), color=self.gray, fontsize=14, italic=True)
            empty_line.alignment = PP_ALIGN.CENTER

        # Add tagcloud
        if data['tags']:
            self.add_picture(data['tagcloud'], self.width/2 + padding, next_line + 0.1, self.width / 2 - 1, chart_height - padding * 2)
        else:
            empty_tf = self.generate_textbox(self.width / 2,  next_line, self.width / 2, chart_height)
            empty_line = self.add_line(empty_tf, _("No Tags Found"), color=self.gray, fontsize=14, italic=True)
            empty_line.alignment = PP_ALIGN.CENTER
        next_line += chart_height + 0.01

        # Add logo
        logo_width = 0.9
        logo_height = 0.25
        logo_left = Inches(self.width / 2 - logo_width / 2)
        try:
            logo_url = os.path.join(settings.STATIC_ROOT, 'img', 'le_login.png')
            self.slide.shapes.add_picture(logo_url, logo_left, Inches(next_line), width=Inches(logo_width), height=Inches(logo_height))
        except IOError:
            logging.warning("Unable to add LE logo")
        next_line += logo_height

        # Add disclaimer
        disclaimer_tf = self.generate_textbox(0, next_line,  self.width, 0.2)
        disclaimer_line = self.add_line(disclaimer_tf, _("This slide was automatically generated by Kolibri Studio, a product of Learning Equality"),
                                        fontsize=7, color=self.gray, append=False)
        disclaimer_line.alignment = PP_ALIGN.CENTER
    def get_pie_chart(self, counts, small_layout=False):
        # Put kind counts in a usable format
        kinds = list(ContentKind.objects.exclude(kind=content_kinds.TOPIC)
                                .order_by('kind')
                                .values_list('kind', flat=True))
        kind_vals = {k: next((c['count'] for c in counts if c['kind_id'] == k), 0) for k in kinds}
        kind_vals = OrderedDict(sorted(kind_vals.items()))
        sizes = [v for k, v in kind_vals.items()]
        total = max(sum(sizes), 1)

        labels = [{
            "text": ' {text} \n{p:.1f}%'.format(
                    text=self.pluralize_constant(v, k),
                    p=float(v)/total * 100.0
            ),
            "count": v
        } for k, v in kind_vals.items()]

        # Create pie chart
        fig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
        wedgeprops = {"edgecolor": "white", 'linewidth': 1, 'linestyle': 'solid', 'antialiased': True}
        wedges, texts = ax.pie(sizes, colors=self.color_selection, wedgeprops=wedgeprops)

        bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
        kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"),
                  bbox=bbox_props, zorder=0, va="center")

        # Add popout labels for the larger layout
        if not small_layout:
            for i, p in enumerate(wedges):
                if not labels[i]['count']:
                    continue

                ang = (p.theta2 - p.theta1)/2. + p.theta1
                y = np.sin(np.deg2rad(ang))
                x = np.cos(np.deg2rad(ang))
                connectionstyle = "angle,angleA=0,angleB={}".format(ang)
                kw["arrowprops"].update({"connectionstyle": connectionstyle, "facecolor": "gray"})
                ax.annotate(labels[i]['text'], xy=(x, y), xytext=(1.35*np.sign(x), 1.4*y),
                            ha="center", fontsize=10, **kw)

        # Add legend for the smaller layout
        else:
            plt.legend(
                loc='center left',
                labels=[l['text'].split('\n')[0] for l in labels],
                prop={'size': 20},
                bbox_to_anchor=(0.7, 0.5),
                bbox_transform=plt.gcf().transFigure
            )

        # Set up size variables for center circle
        center_text_size = 25 if small_layout else 20  # Renders smaller, so text needs to be bigger
        center_text_ratio = 0.75 if small_layout else 0.6

        # Add center circle
        circle = plt.Circle((0, 0), center_text_ratio, fc='white')
        centertext = self.pluralize_constant(sum(sizes), "resource", sep='\n').split('\n')
        plt.annotate(centertext[0], xy=(0, 0.1), fontsize=center_text_size, ha="center")
        plt.annotate(centertext[1], xy=(0, -0.15), fontsize=center_text_size - 5, ha="center")
        fig = plt.gcf()
        fig.gca().add_artist(circle)
        plt.tight_layout()

        # Write chart to image and get encoding
        filepath = self.get_write_to_path(ext="png")
        plt.savefig(filepath, bbox_inches='tight')
        plt.clf()
        plt.close()
        return encode_file_to_base64(filepath, "data:image/png;base64,")
 def get_default_thumbnail_encoding(self):
     try:
         filepath = os.path.join(settings.STATIC_ROOT, 'img', 'kolibri_placeholder.png')
         return encode_file_to_base64(filepath, "data:image/png;base64,")
     except IOError:
         logging.warning("Could not find {}".format(filepath))