Beispiel #1
0
 def test_set_value(self):
     a = Animate(debug=True)
     a.set_value('0;1;2', 'linear', '0', '0 0 0 0', 0, 0, 0)
     self.assertEqual(a.tostring(),
                      '<animate by="0" calcMode="linear" from="0" ' \
                      'keySplines="0 0 0 0" keyTimes="0" to="0" '\
                      'values="0;1;2" />')
Beispiel #2
0
 def test_set_value(self):
     a = Animate(debug=True)
     a.set_value('0;1;2', 'linear', '0', '0 0 0 0', 0, 0, 0)
     self.assertEqual(a.tostring(),
                      '<animate by="0" calcMode="linear" from="0" ' \
                      'keySplines="0 0 0 0" keyTimes="0" to="0" '\
                      'values="0;1;2" />')
Beispiel #3
0
 def test_values_int(self):
     s = Animate(values=(3,), debug=True)
     self.assertEqual(s.tostring(), '<animate values="3" />')
Beispiel #4
0
 def test_values_list(self):
     s = Animate(values=[1, 2, 3], debug=True)
     self.assertEqual(s.tostring(), '<animate values="1;2;3" />')
Beispiel #5
0
 def test_freeze(self):
     a = Animate(debug=True)
     a.freeze()
     self.assertEqual(a.tostring(), '<animate fill="freeze" />')
Beispiel #6
0
 def test_constructor(self):
     a = Animate('x', debug=True)
     self.assertEqual(a.tostring(), '<animate attributeName="x" />')
Beispiel #7
0
 def test_values_int(self):
     s = Animate(values=(3, ), debug=True)
     self.assertEqual(s.tostring(), '<animate values="3" />')
Beispiel #8
0
 def test_values_list(self):
     s = Animate(values=[1, 2, 3], debug=True)
     self.assertEqual(s.tostring(), '<animate values="1;2;3" />')
Beispiel #9
0
 def test_freeze(self):
     a = Animate(debug=True)
     a.freeze()
     self.assertEqual(a.tostring(), '<animate fill="freeze" />')
Beispiel #10
0
 def test_constructor(self):
     a = Animate('x', debug=True)
     self.assertEqual(a.tostring(), '<animate attributeName="x" />')
Beispiel #11
0
 def test_values_colors(self):
     s = Animate(values='#000000;#0000ff;#00ff00;#ff0000', debug=True)
     self.assertEqual(s.tostring(), '<animate values="#000000;#0000ff;#00ff00;#ff0000" />')
Beispiel #12
0
def concentric(datapoints, settings={}):
    # Check arguments
    try:
        assert len(datapoints) > 0
        for dp_set in datapoints:
            for dp in dp_set:
                if dp is None:
                    continue
                if len(set(['continuous', 'discrete']) - set(dp.keys())) > 0:
                    print(
                        'ERROR: every datapoint must have at least "continuous" and "discrete" key values defined'
                    )
                    return ''
    except:
        print(
            'ERROR: datapoints argument should be a two-dimensional iterable of dictionaries'
        )
        return ''

    num_segments = len(datapoints)
    num_versions = len(datapoints[0])
    num_rings = None
    for i in range(num_segments):
        if datapoints[i][0] is not None:
            num_rings = len(datapoints[i][0]['continuous'])
            break

    for setting_name, setting_default in DEFAULT_SETTINGS_CONCENTRIC.items():
        if setting_name not in settings.keys():
            settings[setting_name] = setting_default

    if settings['ring_names'] is None:
        settings['ring_names'] = ['' for _ in range(num_rings)]
    if len(settings['ring_names']) != num_rings:
        print(
            'ERROR: specified number of ring names is inconsistent with the data'
        )
        return ''
    if settings['ring_types'] is None:
        settings['ring_types'] = ['C' for _ in range(num_rings)]
    if len(settings['ring_types']) != num_rings:
        print(
            'ERROR: specified number of ring types is inconsistent with the data'
        )
        return ''
    if settings['ring_polarities'] is None:
        settings['ring_polarities'] = [+1 for _ in range(num_rings)]
    if len(settings['ring_polarities']) != num_rings:
        print(
            'ERROR: specified number of ring polarities is inconsistent with the data'
        )
        return ''

    # Useful calculations
    gap = float(settings['top_gap'])
    sizes = (min(settings['canvas_size']) // 100,
             min(settings['canvas_size']) // 60)
    center = (settings['canvas_size'][0] // 2, settings['canvas_size'][1] // 2)
    full_radius = min(settings['canvas_size']) // 2 - 10
    division_size = full_radius // (num_rings + 2)
    angle_delta = (2 * pi - gap) / float(num_segments)

    # Initialise drawing
    dwg = svgwrite.Drawing(profile='full')

    # Set viewbox attribute for scaling
    dwg.attribs['viewBox'] = '0 0 ' + ' '.join(
        [str(x) for x in settings['canvas_size']])
    dwg.attribs['width'] = '95%'
    dwg.attribs['height'] = '95%'

    # Set HTML attributes for interaction
    # If the user doesn't set an ID for the chart, then it shouldn't have an ID defined in the XML. But, the svg_id variable still needs to be
    # defined for later, to make sure there's something to prepend to the IDs of any child elements that *do* need IDs.
    if settings['svg_id'] is None:
        settings['svg_id'] = 'concentric'
    else:
        dwg.attribs['id'] = settings['svg_id']
    if settings['svg_hidden']:
        dwg.attribs['style'] = 'display: none;'

    # Draw axes
    for ring_id, ring_name in enumerate(settings['ring_names']):
        ring_color = settings['ring_color_sequence'][ring_id] if settings[
            'ring_types'][ring_id] == 'C' else settings['discrete_ring_color']
        dwg.add(
            dwg.circle(r=(ring_id + 2) * division_size,
                       center=center,
                       fill_opacity=0,
                       stroke=ring_color,
                       stroke_width=1,
                       stroke_opacity=1))
        dwg.add(
            dwg.polyline([
                _coords_from_angle(center, (gap / float(25)) *
                                   (i - (20 - 1) / float(2)),
                                   (ring_id + 2) * division_size)
                for i in range(20)
            ],
                         stroke=ring_color,
                         stroke_width=3,
                         stroke_opacity=1,
                         fill_opacity=0))
        dwg.add(
            dwg.text(text=ring_name,
                     insert=_coords_from_angle(
                         center, 0, (ring_id + 2) * division_size + sizes[1]),
                     font_size=sizes[1],
                     font_family='Arial',
                     text_anchor='middle',
                     alignment_baseline='central'))

    # Get means for continuous metrics; get ranges for discrete metrics
    ring_averages = []
    ring_categories = []
    for ring_id in range(num_rings):
        ring_values_cont = []
        ring_values_disc = set()
        for dp_set in datapoints:
            for dp in dp_set:
                if dp is None:
                    continue
                if dp['continuous'] is not None and dp['continuous'][
                        ring_id] is not None:
                    dp['continuous'][ring_id] *= settings['ring_polarities'][
                        ring_id]
                    ring_values_cont.append(dp['continuous'][ring_id])
                if dp['discrete'] is not None and dp['discrete'][
                        ring_id] is not None:
                    ring_values_disc.add(dp['discrete'][ring_id])
        ring_avg = None
        if len(ring_values_cont) > 0:
            ring_avg = sum(ring_values_cont) / len(ring_values_cont)
        ring_averages.append(ring_avg)
        ring_ordered_set = tuple(sorted(ring_values_disc))
        ring_categories.append(ring_ordered_set)

    # Calculate magnitudes for every point and use them to find the min/max for each metric
    magnitudes_by_rsv = []  # RSV = ring, segment, version
    plot_magnitudes_by_rsv = []
    for ring_id in range(num_rings):
        # Calculate average negative delta in the latest dataset
        latest_negative_deltas = []
        for dp_set in datapoints:
            if dp_set[-1] is None or dp_set[-1]['continuous'][ring_id] is None:
                continue
            delta = dp_set[-1]['continuous'][ring_id] - ring_averages[ring_id]
            if delta < 0:
                latest_negative_deltas.append(delta)
        avg_negative_delta = 0
        if len(latest_negative_deltas) > 0:
            avg_negative_delta = sum(latest_negative_deltas) / len(
                latest_negative_deltas)
        # Subtract the average negative delta from all deltas to calculate 'magnitudes'
        ring_magnitudes = []
        for dp_set in datapoints:
            magnitudes_set = []
            for dp in dp_set:
                magnitude = None
                if dp is None or dp['continuous'] is None or dp['continuous'][
                        ring_id] is None:
                    magnitudes_set.append(magnitude)
                    continue
                delta = dp['continuous'][ring_id] - ring_averages[ring_id]
                magnitude = delta - avg_negative_delta
                magnitudes_set.append(magnitude)
            ring_magnitudes.append(magnitudes_set)
        magnitudes_by_rsv.append(ring_magnitudes)
        # Calculate 'plot magnitudes'
        all_ring_magnitudes = [
            magnitude for magnitudes_set in ring_magnitudes
            for magnitude in magnitudes_set if magnitude is not None
        ]
        ring_magnitude_minmax = (0, 0)
        if len(all_ring_magnitudes) > 0:
            ring_magnitude_minmax = (min(all_ring_magnitudes),
                                     max(all_ring_magnitudes))
        ring_plot_magnitudes = []
        for dp_id, dp_set in enumerate(datapoints):
            plot_magnitudes_set = []
            for version_id, dp in enumerate(dp_set):
                magnitude = ring_magnitudes[dp_id][version_id]
                if magnitude is None:
                    plot_magnitude = None
                else:
                    plot_magnitude = 0
                    if magnitude > 0 and ring_magnitude_minmax[1] != 0:
                        plot_magnitude = magnitude / ring_magnitude_minmax[
                            1] * settings['axis_max']
                    elif magnitude < 0 and ring_magnitude_minmax[0] != 0:
                        plot_magnitude = magnitude / ring_magnitude_minmax[
                            0] * settings['axis_min']
                plot_magnitudes_set.append(plot_magnitude)
            ring_plot_magnitudes.append(plot_magnitudes_set)
        plot_magnitudes_by_rsv.append(ring_plot_magnitudes)

    # Calculate plot point coordinates
    line_points_by_rvs = []  # RVS = ring, version, segment
    discrete_segs_by_rvs = []
    for ring_id in range(num_rings):
        ring_base_radius = (ring_id + 2) * division_size
        # Continuous lines
        if settings['ring_types'][ring_id] == 'C':
            line_points = [[] for _ in range(num_versions)]
            # Add zero-point
            for version_id in range(num_versions):
                point = _coords_from_angle(center,
                                           angle_delta * 0.5,
                                           ring_base_radius,
                                           gap=gap)
                line_points[version_id].append(point)
            for segment_id in range(num_segments):
                base_angle = angle_delta * (segment_id + 0.5)
                for version_id, dp in enumerate(dp_set):
                    plot_magnitude = plot_magnitudes_by_rsv[ring_id][
                        segment_id][version_id]
                    if plot_magnitude is None:
                        point = _coords_from_angle(center,
                                                   base_angle,
                                                   ring_base_radius,
                                                   gap=gap)
                    else:
                        plot_radius = ring_base_radius + division_size * plot_magnitude
                        point = _coords_from_angle(center,
                                                   base_angle,
                                                   plot_radius,
                                                   gap=gap)
                    line_points[version_id].append(point)
            line_points_by_rvs.append(line_points)
            discrete_segs_by_rvs.append(None)
        # Discrete segments
        elif settings['ring_types'][ring_id] == 'D':
            ring_discrete_segs = [[] for _ in range(num_versions)]
            for segment_id, dp_set in enumerate(datapoints):
                base_angle = angle_delta * (segment_id + 0.5)
                for version_id, dp in enumerate(dp_set):
                    segment_length = sizes[0]
                    segment_color = settings['discrete_color_sequence'][-1]
                    segment_opacity = 1
                    if dp is not None and dp['discrete'] is not None and dp[
                            'discrete'][ring_id] is not None:
                        category_id = dp['discrete'][ring_id]
                        segment_color = settings['discrete_color_sequence'][
                            category_id]
                    if segment_color == settings['discrete_color_sequence'][
                            -1]:
                        segment_opacity = 0.5
                    segment_points = (_coords_from_angle(
                        center,
                        angle_delta * (segment_id),
                        ring_base_radius - segment_length,
                        gap=gap),
                                      _coords_from_angle(
                                          center,
                                          angle_delta * (segment_id),
                                          ring_base_radius + segment_length,
                                          gap=gap),
                                      _coords_from_angle(
                                          center,
                                          angle_delta * (segment_id + 1),
                                          ring_base_radius + segment_length,
                                          gap=gap),
                                      _coords_from_angle(
                                          center,
                                          angle_delta * (segment_id + 1),
                                          ring_base_radius - segment_length,
                                          gap=gap))
                    ring_discrete_segs[version_id].append(
                        (segment_points, segment_color, segment_opacity))
            line_points_by_rvs.append(None)
            discrete_segs_by_rvs.append(ring_discrete_segs)

    # Draw plot points
    for ring_id in range(num_rings):
        baseline_circle_points = []
        for i in range(settings['baseline_point_resolution'] + 1):
            point_angle = (settings['baseline_point_resolution'] - i) * (
                2 * pi - gap) / float(settings['baseline_point_resolution'])
            point_radius = (ring_id + 2) * division_size
            baseline_circle_points.append(
                _coords_from_angle(center, point_angle, point_radius, gap=gap))
        # Continuous lines
        if settings['ring_types'][ring_id] == 'C':
            line_points = line_points_by_rvs[ring_id][
                -1] + baseline_circle_points
            ring_line = dwg.polyline(
                line_points,
                stroke=settings['ring_color_sequence'][ring_id],
                stroke_width=2,
                stroke_opacity=1,
                fill=settings['ring_color_sequence'][ring_id],
                fill_opacity=0.2,
                id=settings['svg_id'] + '-contline-' + str(ring_id))
            for version_id in range(num_versions):
                line_points = line_points_by_rvs[ring_id][
                    version_id] + baseline_circle_points
                points_string = ' '.join([
                    ','.join([str(c) for c in point]) for point in line_points
                ])
                animation = Animate(
                    values=None,
                    dur=str(settings['animation_length']) + 'ms',
                    begin='indefinite',
                    fill='freeze',
                    attributeName='points',
                    to=points_string,
                    id=settings['svg_id'] + '-animation-' + str(ring_id) +
                    '-' + str(version_id))
                ring_line.add(animation)
            dwg.add(ring_line)
        # Discrete segments
        elif settings['ring_types'][ring_id] == 'D':
            for version_id in range(num_versions):
                group_opacity = 1 if version_id == num_versions - 1 else 0
                segment_group = dwg.g(id=settings['svg_id'] + '-discrete-' +
                                      str(ring_id) + '-' + str(version_id),
                                      opacity=group_opacity)
                segment_point_groups = discrete_segs_by_rvs[ring_id][
                    version_id]
                for segment_points, segment_color, segment_opacity in segment_point_groups:
                    segment_group.add(
                        dwg.polyline(segment_points,
                                     stroke_width=0,
                                     stroke_opacity=0,
                                     fill=segment_color,
                                     fill_opacity=segment_opacity))
                dwg.add(segment_group)

    # Draw missing-data shade
    if settings['missing_data_shade_enabled']:
        for version_id in range(num_versions):
            group_opacity = 1 if version_id == num_versions - 1 else 0
            shade_group = dwg.g(id=settings['svg_id'] + '-shade-' +
                                str(version_id),
                                opacity=group_opacity)
            for dp_id, dp_set in enumerate(datapoints):
                if dp_set[version_id] is None:
                    shade_group.add(
                        dwg.polygon([
                            center,
                            _coords_from_angle(center,
                                               angle_delta * dp_id,
                                               full_radius + 5,
                                               gap=gap),
                            _coords_from_angle(center,
                                               angle_delta * (dp_id + 1),
                                               full_radius + 5,
                                               gap=gap)
                        ],
                                    stroke_opacity=0,
                                    fill=settings['missing_data_shade_color'],
                                    fill_opacity=1))
                    """
                    shade_group.add(dwg.circle(r=sizes[0]//2,
                                               center=_coords_from_angle(center, angle_delta*(dp_id+0.5), full_radius-1.5*sizes[1], gap=gap),
                                               fill=COLORS['BLACK'],
                                               fill_opacity=1,
                                               stroke_opacity=0))
                    """
            dwg.add(shade_group)

    # Draw outer markers
    if settings['apply_markers']:
        any_markers = False
        for version_id in range(num_versions):
            group_opacity = 1 if version_id == num_versions - 1 else 0
            markers_group = dwg.g(id=settings['svg_id'] + '-markers-' +
                                  str(version_id),
                                  opacity=group_opacity)
            for dp_id, dp_set in enumerate(datapoints):
                if dp_set[version_id] is None:
                    continue
                if 'marker' in dp_set[version_id] and dp_set[version_id][
                        'marker'] == True:
                    any_markers = True
                    markers_group.add(
                        dwg.line(
                            _coords_from_angle(center,
                                               angle_delta * (dp_id + 0.2),
                                               full_radius - sizes[0] * 0.1,
                                               gap=gap),
                            _coords_from_angle(center,
                                               angle_delta * (dp_id + 0.8),
                                               full_radius - sizes[0] * 0.9,
                                               gap=gap),
                            stroke=settings['marker_color'],
                            stroke_width=3,
                            stroke_opacity=1))
                    markers_group.add(
                        dwg.line(
                            _coords_from_angle(center,
                                               angle_delta * (dp_id + 0.2),
                                               full_radius - sizes[0] * 0.9,
                                               gap=gap),
                            _coords_from_angle(center,
                                               angle_delta * (dp_id + 0.8),
                                               full_radius - sizes[0] * 0.1,
                                               gap=gap),
                            stroke=settings['marker_color'],
                            stroke_width=3,
                            stroke_opacity=1))
            dwg.add(markers_group)
        if any_markers:
            dwg.add(
                dwg.text(text=settings['marker_label'],
                         insert=_coords_from_angle(
                             center, 0, full_radius - sizes[0] * 0.1),
                         font_size=sizes[1],
                         font_family='Arial',
                         text_anchor='middle',
                         alignment_baseline='central'))

    # Draw outer rings
    dwg.add(
        dwg.circle(r=full_radius - 2 * sizes[1],
                   center=center,
                   fill_opacity=0,
                   stroke=COLORS['BLACK'],
                   stroke_width=1,
                   stroke_opacity=0.5))
    dwg.add(
        dwg.circle(r=full_radius - 1 * sizes[1],
                   center=center,
                   fill_opacity=0,
                   stroke=COLORS['BLACK'],
                   stroke_width=1,
                   stroke_opacity=0.5))
    for i in range(num_segments + 1):
        dwg.add(
            dwg.line(_coords_from_angle(center,
                                        angle_delta * i,
                                        full_radius - 2 * sizes[1],
                                        gap=gap),
                     _coords_from_angle(center,
                                        angle_delta * i,
                                        full_radius - 1 * sizes[1],
                                        gap=gap),
                     stroke=COLORS['BLACK'],
                     stroke_width=1,
                     stroke_opacity=0.5))

    # Draw segment selector
    if settings['segment_selector_enabled']:
        center_point = angle_delta * 0.5
        selector_points = (_coords_from_angle(center,
                                              center_point,
                                              full_radius - 1.5 * sizes[1],
                                              gap=gap),
                           _coords_from_angle(center,
                                              center_point - 0.02,
                                              full_radius - 0.5 * sizes[1],
                                              gap=gap),
                           _coords_from_angle(center,
                                              center_point - 0.02,
                                              full_radius + 5,
                                              gap=gap),
                           _coords_from_angle(center,
                                              center_point + 0.02,
                                              full_radius + 5,
                                              gap=gap),
                           _coords_from_angle(center,
                                              center_point + 0.02,
                                              full_radius - 0.5 * sizes[1],
                                              gap=gap))
        dwg.add(
            dwg.polygon(selector_points,
                        stroke=COLORS['BLACK'],
                        stroke_width=2,
                        stroke_opacity=1,
                        fill=COLORS['D_GREY'],
                        fill_opacity=0.2,
                        id=settings['svg_id'] + '-selector'))

    # Draw interaction segments
    if settings['interaction_segments_enabled']:
        for i in range(num_segments):
            dwg.add(
                dwg.polygon([
                    center,
                    _coords_from_angle(
                        center, angle_delta * i, full_radius + 5, gap=gap),
                    _coords_from_angle(center,
                                       angle_delta * (i + 1),
                                       full_radius + 5,
                                       gap=gap)
                ],
                            stroke=COLORS['BLACK'],
                            stroke_width=1,
                            stroke_opacity=0,
                            fill=COLORS['L_GREY'],
                            fill_opacity=0,
                            onmousedown=settings['segment_function_name'] +
                            '(1, ' + str(i) + ');',
                            onmouseover=settings['segment_function_name'] +
                            '(2, ' + str(i) + ');',
                            onmouseup=settings['segment_function_name'] +
                            '(3, ' + str(i) + ');',
                            id=settings['svg_id'] + '-intseg-' + str(i)))
        dwg.add(
            dwg.circle(r=1.5 * division_size,
                       center=center,
                       fill=COLORS['WHITE'],
                       fill_opacity=1,
                       stroke_opacity=0))

    # Draw center text
    if settings['center_text_1'] is not None:
        dwg.add(
            dwg.text(text=settings['center_text_1'],
                     insert=(center[0], center[1] - 1.5 * sizes[1]),
                     font_size=1.5 * sizes[1],
                     font_family='Arial',
                     font_weight='bold',
                     text_anchor='middle',
                     alignment_baseline='central'))
    if settings['center_text_2'] is not None:
        dwg.add(
            dwg.text(text=settings['center_text_2'],
                     insert=(center[0], center[1] + 1 * sizes[1]),
                     font_size=sizes[1],
                     font_family='Arial',
                     text_anchor='middle',
                     alignment_baseline='central'))
    if settings['center_text_3'] is not None:
        dwg.add(
            dwg.text(text=settings['center_text_3'],
                     insert=(center[0], center[1] + 3 * sizes[1]),
                     font_size=sizes[1],
                     font_family='Arial',
                     text_anchor='middle',
                     alignment_baseline='central',
                     fill=COLORS['L_GREY']))
    return dwg.tostring()