diff --git a/CHANGELOG.md b/CHANGELOG.md index b77079de..8c6e23a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased => [0.7.2] +### Add +- Add Piechart drawing +- Add Piechart drawing in mutliplot ## [0.7.1] ### Add diff --git a/plot_data/core.py b/plot_data/core.py index 45469bba..8635019d 100644 --- a/plot_data/core.py +++ b/plot_data/core.py @@ -716,6 +716,30 @@ def __init__(self, x_variable: str, y_variable: str, PlotDataObject.__init__(self, type_='scatterplot', name=name) +class PieChart(PlotDataObject): + """ + A class for drawing pie plots. + + :param data_samples: [{'name_param_1': any,..., 'name_param_p': any},..., + {'name_param_1': any,..., 'name_param_p': any}] + :type data_samples: List[dict] + :param slicing_variable: variable that you want to use to fill pie parts + :type slicing_variable: str + """ + + def __init__(self, slicing_variable: str, + data_samples: List[Any] = None, + name: str = ''): + + self.slicing_variable = slicing_variable + if not data_samples: + self.data_samples = [] + else: + self.data_samples = data_samples + + PlotDataObject.__init__(self, type_='piechart', name=name) + + class ScatterMatrix(PlotDataObject): def __init__(self, elements: List[Any] = None, axes: List[str] = None, point_style: PointStyle = None, surface_style: SurfaceStyle = None, @@ -1151,6 +1175,8 @@ def plot_canvas(plot_data_object: Subclass[PlotDataObject], template = templates.histogram_template elif plot_type == "scattermatrix": template = templates.scatter_matrix_template + elif plot_type == "piechart": + template = templates.piechart_template else: raise NotImplementedError('Type {} not implemented'.format(plot_type)) diff --git a/plot_data/templates.py b/plot_data/templates.py index 99dced8a..13671338 100644 --- a/plot_data/templates.py +++ b/plot_data/templates.py @@ -225,3 +225,35 @@ ''') + + +piechart_template = Template(''' + + + + + +
+ + + + + + +
+ +''') diff --git a/script/multiple_plots.py b/script/multiple_plots.py index cd8134f9..a0923938 100644 --- a/script/multiple_plots.py +++ b/script/multiple_plots.py @@ -16,15 +16,15 @@ a list of vectors (dictionaries) that are displayed through different representations such as parallel plots and scatter plots """ -elements = [] +data_samples = [] -nb_elements = 50 +nb_samples = 50 available_colors = [colors.VIOLET, colors.BLUE, colors.GREEN, colors.RED, colors.YELLOW, colors.CYAN, colors.ROSE] directions = ['north', 'south', 'west', 'east'] -for i in range(nb_elements): +for i in range(nb_samples): random_color = available_colors[random.randint(0, len(available_colors) - 1)] random_direction = directions[random.randint(0, len(directions) - 1)] - elements.append({'x': random.uniform(0, 200), + data_samples.append({'x': random.uniform(0, 200), 'y': random.uniform(0, 100), 'color': random_color, 'direction': random_direction}) @@ -35,12 +35,17 @@ """Scatterplots""" scatterplot1 = plot_data.Scatter(x_variable='x', y_variable='y') +piechart1 = plot_data.PieChart(data_samples=data_samples, + slicing_variable='mass') scatterplot2 = plot_data.Scatter(x_variable='y', y_variable='color', point_style=plot_data.PointStyle(shape='square')) # optional argument that changes # points' appearance -scatterplot3 = plot_data.Scatter(x_variable='x', y_variable='direction') +# scatterplot3 = plot_data.Scatter(x_variable='x', y_variable='direction') +piechart1 = plot_data.PieChart(data_samples=data_samples, + slicing_variable='x') + """PrimitiveGroupContainers""" contour = plot_data.Contour2D(plot_data_primitives=[plot_data.LineSegment2D([1, 1], [1, 2]), @@ -67,13 +72,35 @@ """Creating the multiplot""" plots = [parallelplot1, parallelplot2, scatterplot1, - scatterplot2, scatterplot3, graph2d, primitive_group_container, + scatterplot2, piechart1, graph2d, primitive_group_container, + histogram, graph2d, primitive_group_container, histogram] -# plots = [scatterplot1, scatterplot2] - -multiplot = plot_data.MultiplePlots(plots=plots, elements=elements, +multiplot = plot_data.MultiplePlots(plots=plots, elements=data_samples, initial_view_on=True) # Display plot_data.plot_canvas(plot_data_object=multiplot, debug_mode=True) + +plots2 = [piechart1, piechart1, piechart1, piechart1, piechart1, piechart1, + piechart1, piechart1, piechart1, piechart1, piechart1, piechart1, + piechart1, piechart1, piechart1, piechart1, piechart1] + +multiplot2 = plot_data.MultiplePlots(plots=plots2, elements=data_samples, + initial_view_on=True) + +# Display +plot_data.plot_canvas(plot_data_object=multiplot2, debug_mode=True) + + +plots3 = [scatterplot2, scatterplot2, scatterplot2, scatterplot2, scatterplot2, scatterplot2, + scatterplot2, scatterplot2, scatterplot2, scatterplot2] + +multiplot3 = plot_data.MultiplePlots(plots=plots3, elements=data_samples, + initial_view_on=True) + +# Display +plot_data.plot_canvas(plot_data_object=multiplot3, debug_mode=True) + + + diff --git a/script/pie_chart.py b/script/pie_chart.py new file mode 100644 index 00000000..5ed52c41 --- /dev/null +++ b/script/pie_chart.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Jun 3 16:18:31 2022 + +@author: tanguy +""" + +import plot_data +import plot_data.colors as colors +import random + + +data_samples = [] +SHAPES = ['round', 'square', 'triangle', 'ellipse'] +COLORS = [colors.RED, colors.BLUE, colors.GREEN, colors.YELLOW, colors.ORANGE, colors.VIOLET] +for i in range(50): + random_shape = SHAPES[random.randint(0, len(SHAPES) - 1)] + random_color = COLORS[random.randint(0, len(SHAPES) - 1)] + data_samples.append({'mass': random.uniform(0, 50), + 'length': random.uniform(0, 100), + 'shape': random_shape, + 'color': random_color + }) + + +piechart1 = plot_data.PieChart(data_samples=data_samples, + slicing_variable='mass') + +piechart2 = plot_data.PieChart(data_samples=data_samples, + slicing_variable='length') + + +plot_data.plot_canvas(plot_data_object=piechart1, debug_mode=True) +plot_data.plot_canvas(plot_data_object=piechart2, debug_mode=True) \ No newline at end of file diff --git a/src/multiplots.ts b/src/multiplots.ts index 31109c83..7f9e7027 100644 --- a/src/multiplots.ts +++ b/src/multiplots.ts @@ -1,7 +1,7 @@ import {PlotData, Interactions} from './plot-data'; import {Point2D} from './primitives'; import { Attribute, PointFamily, check_package_version, Window, TypeOf, equals, Sort, download } from './utils'; -import { PlotContour, PlotScatter, ParallelPlot, PrimitiveGroupContainer, Histogram } from './subplots'; +import { PlotContour, PlotScatter, ParallelPlot, PrimitiveGroupContainer, Histogram, PlotPieChart } from './subplots'; import { List, Shape, MyObject } from './toolbox'; import { string_to_hex, string_to_rgb, rgb_to_string } from './color_conversion'; @@ -85,6 +85,8 @@ export class MultiplePlots { newObject = new ParallelPlot(this.dataObjects[i], this.sizes[i]['width'], this.sizes[i]['height'], buttons_ON, this.initial_coords[i][0], this.initial_coords[i][1], canvas_id, true); } else if (object_type_ === 'primitivegroup') { newObject = new PlotContour(this.dataObjects[i], this.sizes[i]['width'], this.sizes[i]['height'], buttons_ON, this.initial_coords[i][0], this.initial_coords[i][1], canvas_id, true); + } else if (object_type_ === 'piechart') { + newObject = new PlotPieChart(this.dataObjects[i], this.sizes[i]['width'], this.sizes[i]['height'], buttons_ON, this.initial_coords[i][0], this.initial_coords[i][1], canvas_id, true); } else if (object_type_ === 'primitivegroupcontainer') { newObject = new PrimitiveGroupContainer(this.dataObjects[i], this.sizes[i]['width'], this.sizes[i]['height'], buttons_ON, this.initial_coords[i][0], this.initial_coords[i][1], canvas_id, true); if (this.dataObjects[i]['association']) { @@ -109,6 +111,7 @@ export class MultiplePlots { this.display_order.push(i); this.to_display_plots.push(i); } + this.mouse_interaction(); if (buttons_ON) { @@ -1053,27 +1056,30 @@ export class MultiplePlots { this.small_length_nb_objects = Math.min(Math.ceil(nbObjectsDisplayed/2), Math.floor(Math.sqrt(nbObjectsDisplayed))); this.big_length_nb_objects = Math.ceil(nbObjectsDisplayed/this.small_length_nb_objects); this.sorted_list = this.getSortedList(); - // let big_length_step = this[big_length]/big_length_nb_objects; - // let small_length_step = this[small_length]/small_length_nb_objects; + // let big_length_step = this[big_length]/this.big_length_nb_objects; + // let small_length_step = this[small_length]/this.small_length_nb_objects; let blank_space = this.padding || 0.01*this[small_length]; let big_length_step = (this[big_length] - (this.big_length_nb_objects + 1)*blank_space)/this.big_length_nb_objects; let small_length_step = (this[small_length] - (this.small_length_nb_objects + 1)*blank_space)/this.small_length_nb_objects; - for (let i=0; i=0) && (pr_x<=this.width) && - (pr_y+pr_h>0) && (pr_y<=this.height); + let pr_x = scaleX * d.primitives[i].minX + mvx; + let pr_y = scaleY * d.primitives[i].minY + mvy; + let pr_w = scaleX * (d.primitives[i].maxX - d.primitives[i].minX); + let pr_h = scaleY * (d.primitives[i].maxY - d.primitives[i].minY); + var is_inside_canvas = (pr_x + pr_w >= 0) && (pr_x <= this.width) && + (pr_y + pr_h > 0) && (pr_y <= this.height); if (need_check.includes(d.primitives[i].type_) && !is_inside_canvas) continue; if (d.primitives[i].type_ === 'contour') { this.draw_contour(hidden, mvx, mvy, scaleX, scaleY, d.primitives[i]); @@ -370,7 +370,7 @@ export abstract class PlotData { this.draw_wire(hidden, d.primitives[i]); // tooltips drawn in mouse_move_interaction() } - this.context.closePath(); + this.context.closePath(); } } } @@ -394,8 +394,8 @@ export abstract class PlotData { this.context.setLineDash([]); } - - draw_contour(hidden, mvx, mvy, scaleX, scaleY, d:Contour2D) { + + draw_contour(hidden, mvx, mvy, scaleX, scaleY, d: Contour2D) { if (hidden) { this.context.fillStyle = d.hidden_color; } else { @@ -407,7 +407,7 @@ export abstract class PlotData { this.context.fill(); this.context.globalAlpha = 1; if (d.surface_style.hatching != null) { - this.context.fillStyle = this.context.createPattern(d.surface_style.hatching.canvas_hatching,'repeat'); + this.context.fillStyle = this.context.createPattern(d.surface_style.hatching.canvas_hatching, 'repeat'); } if (this.select_on_mouse == d) { this.context.fillStyle = this.color_surface_on_mouse; @@ -422,15 +422,15 @@ export abstract class PlotData { for (var j = 0; j < d.plot_data_primitives.length; j++) { let elem = d.plot_data_primitives[j]; if (j == 0) var first_elem = true; else first_elem = false; - if (elem.type_ == 'linesegment2d') elem.draw(this.context, first_elem, mvx, mvy, scaleX, scaleY, this.X, this.Y); - else elem.contour_draw(this.context, first_elem, mvx, mvy, scaleX, scaleY, this.X, this.Y); + if (elem.type_ == 'linesegment2d') elem.draw(this.context, first_elem, mvx, mvy, scaleX, scaleY, this.X, this.Y); + else elem.contour_draw(this.context, first_elem, mvx, mvy, scaleX, scaleY, this.X, this.Y); } this.context.stroke(); this.context.fill(); this.context.setLineDash([]); } - draw_circle(hidden, mvx, mvy, scaleX, scaleY, d:Circle2D) { + draw_circle(hidden, mvx, mvy, scaleX, scaleY, d: Circle2D) { if (hidden) { this.context.fillStyle = d.hidden_color; d.draw(this.context, mvx, mvy, scaleX, scaleY, this.X, this.Y); @@ -445,7 +445,7 @@ export abstract class PlotData { this.context.fill(); this.context.globalAlpha = 1; if (d.surface_style.hatching != null) { - this.context.fillStyle = this.context.createPattern(d.surface_style.hatching.canvas_hatching,'repeat'); + this.context.fillStyle = this.context.createPattern(d.surface_style.hatching.canvas_hatching, 'repeat'); this.context.fill(); } if (this.select_on_mouse == d) { @@ -462,7 +462,7 @@ export abstract class PlotData { this.context.setLineDash([]); } - draw_point(hidden, mvx, mvy, scaleX, scaleY, d:Point2D) { + draw_point(hidden, mvx, mvy, scaleX, scaleY, d: Point2D) { if (hidden) { this.context.fillStyle = d.hidden_color; } else { @@ -520,11 +520,11 @@ export abstract class PlotData { if (this.log_scale_x) cx = Math.log10(cx); if (this.log_scale_y) cy = -Math.log10(-cy); - var x = scaleX*cx+ mvx; - var y = scaleY*cy + mvy; + var x = scaleX * cx + mvx; + var y = scaleY * cy + mvy; this.pointLength = d.size; - var is_inside_canvas = ((x + this.pointLength>=0) && (x - this.pointLength <= this.width) && (y + this.pointLength >= 0) && (y - this.pointLength <= this.height)); + var is_inside_canvas = ((x + this.pointLength >= 0) && (x - this.pointLength <= this.width) && (y + this.pointLength >= 0) && (y - this.pointLength <= this.height)); if (is_inside_canvas === true) { this.context.beginPath(); d.draw(this.context, mvx, mvy, scaleX, scaleY, this.X, this.Y, this.log_scale_x, this.log_scale_y); @@ -534,65 +534,65 @@ export abstract class PlotData { } } - draw_axis(mvx, mvy, scaleX, scaleY, d:Axis, log_scale_x, log_scale_y) { // Only used by graph2D - d.draw_horizontal_axis(this.context, mvx, scaleX, this.width, this.height, this.init_scaleX, this.minX, this.maxX, this.scroll_x, + draw_axis(mvx, mvy, scaleX, scaleY, d: Axis, log_scale_x, log_scale_y) { // Only used by graph2D + d.draw_horizontal_axis(this.context, mvx, scaleX, this.width, this.height, this.init_scaleX, this.minX, this.maxX, this.scroll_x, this.decalage_axis_x, this.decalage_axis_y, this.X, this.Y, this.plotObject['attribute_names'][0], log_scale_x); d.draw_vertical_axis(this.context, mvy, scaleY, this.width, this.height, this.init_scaleY, this.minY, this.maxY, this.scroll_y, this.decalage_axis_x, this.decalage_axis_y, this.X, this.Y, this.plotObject['attribute_names'][1], log_scale_y); - this.x_nb_digits = Math.max(0, 1-Math.floor(Math.log10(d.x_step))); - this.y_nb_digits = Math.max(0, 1-Math.floor(Math.log10(d.y_step))); + this.x_nb_digits = Math.max(0, 1 - Math.floor(Math.log10(d.x_step))); + this.y_nb_digits = Math.max(0, 1 - Math.floor(Math.log10(d.y_step))); } - draw_scatterplot_axis(d:Axis, lists, to_display_attributes) { - d.draw_scatter_axis(this.context, this.originX, this.originY, this.scaleX, this.scaleY, this.width, this.height, - this.init_scaleX, this.init_scaleY, lists, to_display_attributes, this.scroll_x, this.scroll_y, - this.decalage_axis_x, this.decalage_axis_y, this.X, this.Y, this.width, + draw_scatterplot_axis(d: Axis, lists, to_display_attributes) { + d.draw_scatter_axis(this.context, this.originX, this.originY, this.scaleX, this.scaleY, this.width, this.height, + this.init_scaleX, this.init_scaleY, lists, to_display_attributes, this.scroll_x, this.scroll_y, + this.decalage_axis_x, this.decalage_axis_y, this.X, this.Y, this.width, this.height, this.log_scale_x, this.log_scale_y); - this.x_nb_digits = Math.max(0, 1-Math.floor(Math.log10(d.x_step))); - this.y_nb_digits = Math.max(0, 1-Math.floor(Math.log10(d.y_step))); + this.x_nb_digits = Math.max(0, 1 - Math.floor(Math.log10(d.x_step))); + this.y_nb_digits = Math.max(0, 1 - Math.floor(Math.log10(d.y_step))); this.context.closePath(); this.context.fill(); } - draw_tooltip(d:Tooltip, mvx, mvy, point_list, initial_point_list, elements, mergeON, axes:any[]) { + draw_tooltip(d: Tooltip, mvx, mvy, point_list, initial_point_list, elements, mergeON, axes: any[]) { if (d['type_'] == 'tooltip') { this.tooltip_ON = true; - d.manage_tooltip(this.context, mvx, mvy, this.scaleX, this.scaleY, this.width, this.height, this.tooltip_list, + d.manage_tooltip(this.context, mvx, mvy, this.scaleX, this.scaleY, this.width, this.height, this.tooltip_list, this.X, this.Y, this.x_nb_digits, this.y_nb_digits, point_list, initial_point_list, elements, mergeON, axes, this.log_scale_x, this.log_scale_y); } } find_min_dist(d, mvx, mvy, step) { - var x0 = this.scaleX*d.point_list[0].cx + mvx; - var y0 = this.scaleY*d.point_list[0].cy + mvy; - var x1 = this.scaleX*d.point_list[step].cx + mvx; - var y1 = this.scaleY*d.point_list[step].cy + mvy; - var min_dist = this.distance([x0,y0],[x1,y1]); - for (var i=1; i