Getting started with Plotapi Pareto Front

Preamble¶

In [1]:
from plotapi import ParetoFront

ParetoFront.set_license("your username", "your license key")

Introduction¶

Our first Plotapi Pareto Front Diagram!

Plotapi Pareto Front is a beautiful and feature rich take on non-dominated sorting over time. As we can see, we have set our license details in the preamble with ParetoFront.set_license().

Dataset¶

Plotapi Pareto Front expects at minimum a list of dictionary items, these will define bi-objective solutions over time.

In [2]:
samples = [
    'order': 20200101, 'objv_1': 40, 'objv_2': 12,   
    'order': 20200101, 'objv_1': 40, 'objv_2': 12,   
    'order': 20200101, 'objv_1': 40, 'objv_2': 12,   

    'order': 20200104, 'objv_1': 40, 'objv_2': 12,   
    'order': 20200104, 'objv_1': 40, 'objv_2': 12,   
    'order': 20200104, 'objv_1': 40, 'objv_2': 14,   

    'order': 20200109, 'objv_1': 40, 'objv_2': 12,   
    'order': 20200109, 'objv_1': 45, 'objv_2': 12,   
    'order': 20200109, 'objv_1': 45, 'objv_2': 10,   

    'order': 20200112, 'objv_1': 50, 'objv_2': 12,   
    'order': 20200112, 'objv_1': 50, 'objv_2': 11,   
    'order': 20200112, 'objv_1': 50, 'objv_2': 10,   

    'order': 20200115, 'objv_1': 50, 'objv_2': 12,   
    'order': 20200115, 'objv_1': 50, 'objv_2': 12,   
    'order': 20200115, 'objv_1': 50, 'objv_2': 12,   

    'order': 20200115, 'objv_1': 50, 'objv_2': 12,   
    'order': 20200115, 'objv_1': 50, 'objv_2': 12,   
    'order': 20200115, 'objv_1': 50, 'objv_2': 12,   

    'order': 20200120, 'objv_1': 60, 'objv_2': 8,   
    'order': 20200120, 'objv_1': 60, 'objv_2': 8,   
    'order': 20200120, 'objv_1': 60, 'objv_2': 8,   

    'order': 20200123, 'objv_1': 60, 'objv_2': 8,   
    'order': 20200123, 'objv_1': 60, 'objv_2': 8,   
    'order': 20200123, 'objv_1': 60, 'objv_2': 8,   

    'order': 20200123, 'objv_1': 80, 'objv_2': 3,  

    'order': 20200123, 'objv_1': 30, 'objv_2': 20,  

    'order': 20200125, 'objv_1': 30, 'objv_2': 20,  
    'order': 20200125, 'objv_1': 10, 'objv_2': 20, 

    'order': 20200129, 'objv_1': 120, 'objv_2': 50,  
    'order': 20200129, 'objv_1': 50, 'objv_2': 120,   
    'order': 20200129, 'objv_1': 50, 'objv_2': 120,   
    'order': 20200129, 'objv_1': 50, 'objv_2': 120,    

    'order': 20200130, 'objv_1': 100, 'objv_2': 100,      
]

We can see that each dictionary item has three properties:

  • order which determines with time period this item belongs to. This should be numerical, but can be formatted e.g. as dates.
  • objv_1 the first objective value, e.g. “weight”.
  • objv_2 the second objective value, e.g. “reps”.

We can also populate the events structure to present event text at specific times.

In [3]:
events = [
    
        "order": 20200101,
        "event": "My first ever gym visit!"
    ,
    
        "order": 20200109,
        "event": "I went a little heavier today!"
    ,
    
        "order": 20200120,
        "event": "Three solid sets of 60 kg!"
    ,
    
        "order": 20200129,
        "event": "Broke some records today!"
    
]

Visualisation¶

Creating our first Pareto Front Diagram is as easy as calling Plotapi with our one input.

Here we’re using .show() which outputs to a Jupyter Notebook cell, however, we may want to output to an HTML file with .to_html() instead. More on the different output methods later!

Be sure to interact with the visualisation to see what the default settings can do!

In [4]:
ParetoFront(samples, events=events, title="Bench Press",
            objv_1_unit=" kg", objv_2_unit=" reps",
            x_label="Weight(kg)", y_label="Reps").show()


Plotapi – ParetoFront Diagram

.tippy-content
font-family: “Lato”, sans-serif !important;

#plotapi-chart-ffb71dbc, #featured-plotapi-chart-ffb71dbc
font-size: 16px;
font-family: “Lato”, sans-serif !important;
text-align: center;
fill: #454545;

#plotapi-chart-ffb71dbc svg, #featured-plotapi-chart-ffb71dbc svg
max-width: 740px;

#plotapi-chart-ffb71dbc
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: Helvetica, sans-serif;

#plotapi-chart-ffb71dbc svg
margin-left: auto;
margin-right: auto;

#plotapi-chart-ffb71dbc path
fill: none;
stroke-linecap: round

#plotapi-chart-ffb71dbc .x_axis .tick:not(:first-of-type) line
stroke: #999;
stroke-opacity: 0.5;
stroke-dasharray: 2, 2

#plotapi-chart-ffb71dbc .x_axis .tick:first-of-type line
stroke: #999;
stroke-opacity: 0.5;
stroke-dasharray: 2, 2

#plotapi-chart-ffb71dbc .y_axis .tick:not(:first-of-type) line
stroke: #999;
stroke-opacity: 0.5;
stroke-dasharray: 2, 2

#plotapi-chart-ffb71dbc .y_axis .tick:first-of-type line
stroke: #999;
stroke-opacity: 0.5;
stroke-dasharray: 2, 2

#plotapi-chart-ffb71dbc .tick text
fill: #999;

#plotapi-chart-ffb71dbc .domain
stroke-width: 0;

(function() {
var jupyter_classic = !(typeof(IPython)===”undefined”);
var dependencies_paths =
‘d3’: ‘https://plotapi.com/static/js/d3.v7.min’,
‘@popperjs/core’: ‘https://plotapi.com/static/js/popper.min’,
‘tippy’: ‘https://plotapi.com/static/js/tippy.min’,
‘pako’: ‘https://plotapi.com/static/js/pako.min’

if(jupyter_classic)
require.config(

paths: dependencies_paths

);

require([‘d3′,’tippy’,’pako’], function(d3, tippy,pako)
window.d3 = d3;
window.tippy = tippy;
window.pako = pako;
plotapi_plot();
);

else
var dependencies = Object.values(dependencies_paths);

function dependency_loader(dependencies_loaded)
var script = document.createElement(“script”);
script.type = “text/javascript”;
script.src = dependencies[dependencies_loaded] + “.js”;

script.onload = function ()
if(dependencies_loaded < dependencies.length-1)
dependency_loader(dependencies_loaded+1)

else
plotapi_plot();

;
document.body.appendChild(script);

dependency_loader(0);

function plotapi_plot(){
function color(index)
ranks = n_final_ranks + 1;

if (index == 0)
return "#ff0000";

index = index – 1;
ranks = ranks – 1;

index = (ranks) – index;

var ratio = index / (ranks);
return d3.interpolateRainbow(ratio);

function darkenColor(color, factor)
return d3.color(color).darker(factor)

var sequence = [20200101, 20200104, 20200109, 20200112, 20200115, 20200120, 20200123, 20200125, 20200129, 20200130];

var sequence_index = 0;

var last_sequence_index;
var dataset;
var pareto_dataset;
var dominated_dataset;
var n_ranks;
var n_final_ranks;
var limit_y_axis;
var limit_x_axis;
var range_x;
var range_y;
var pareto_range_x;
var pareto_range_y;
var zoomed = true;
var zoomed_user = true;

var y_scale;
var x_scale;
var y_axis;
var x_axis;

var zoom_timeout;
var auto_proceed_timeout;
var auto_proceed_interrupt = false;

var dataset_collection = "eJztmstqwzAQRX/FeB2KHlaa9ldCKH1tWojBtN2E/nvdQsF2LI0nkp1RcneWF4fRaGbuxfJ2eyjr5uW1Ke8Lo4xSWulVUdZPb18Pun1XqRv1vzbtWpu/dfO4f29Xv4/P9ef+o32236tCJmu3KqJ2qTs0FxnZoqwqS1blO0md9CTXCbM/O+vE7J+ZFTjJEdZdwrj6LDd1VkyIa8hSvrjiq9UkrLBFWYwKE8SarVrJuBjVqnlxBavVEixtuiw3WXFPYGnOHgmW8uUeHZkZCx2ZqCNvxXbkCMsl3CNYC7EwWbNiYbJisl7A1LlOllFd1nrA2vg/hMlEQTqyYkE6IB1iRiFYi0jHWDsKRdmrQm0GKMsZzn2UHaCM8rMg2VmxINmQbDESBBYkOzudzV2yzRjLzcXSQVbaS3JLJF8siyH/gliV/yTjrAQZF8NKUNLIsRJVQivRzVeslSDln2ElSLvEkDNyj2CdjcUQbeoPK6Go6aJ9CaiglaCGM8dKdFmxVoLHClsJagia3qDXZgBzgU8cFOy4E72w0dsSITDYr6xYsF+wX2LsBFiwX9l5Jtgv2C/JMKv6+T9SjtC13O4HuNtMMQ==";
var events = "eJxtzTELwjAQBeC/8szcIe0g6Ojm4OYmDsFc26OpgcsRCeJ/Ny5SsdvxuO+9y9NE8SRmj8521ra2bWAo011rZE4FPUtS1EQwlBmZE+vGvBr8yd1SHvGoFxwCqwbCSC5z7dDoXVn1nV368yhESDGwRyJNiD22FtOwbn+2DxKnj50JQrf6mb6z1zcvYUkk";

var strData = atob(dataset_collection);

var charData = strData.split('').map(function(x)return x.charCodeAt(0););

var binData = new Uint8Array(charData);

dataset_collection = JSON.parse(pako.inflate(binData, to: 'string' ));

strData = atob(events);

charData = strData.split('').map(function(x)return x.charCodeAt(0););

binData = new Uint8Array(charData);

events = JSON.parse(pako.inflate(binData, to: 'string' ));

const svg = d3
.select("#plotapi-chart-ffb71dbc")
.append("svg")
.style("max-width", 740 + 40 + 20)
.attr(
"viewBox",
"0 0 " +
(740 + 40 + 20) +
" " +
(650 + 110 + 40)
)
.attr("class", "plotapi-plot")
.attr("preserveAspectRatio", "xMinYMin meet")
.style("background-color", "white")
.style("border", 2)
.append("g")
.attr("id", "plotapi-chart-ffb71dbc_svg")
.attr(
"transform",
"translate(" + 40 + "," + 110 + ")"
);

const axis_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("axis_group", true);
var y_axis_line = axis_group
.append("g")
.attr("class", "axis y_axis")
.attr("transform", `translate(0, 0))`)
var x_axis_line = axis_group
.append("g")
.attr("class", "axis x_axis")
.attr("transform", `translate(0, ` + 650 + `)`)

var x_axis_label = axis_group
.append("text")
.classed("axis_label", true)
.attr("x", 740 / 2)
.attr("y", 650 + (40 / 2))
.style("font-size", 18)
.attr("text-anchor", "middle")
.style("dominant-baseline", "hanging")
.attr("pointer-events", "none")
.text("Weight(kg)");

var y_axis_label = axis_group
.append("text")
.classed("axis_label", true)
.attr("x", -650 / 2)
.attr("y", -(40 / 2))
.style("font-size", 18)
.attr("text-anchor", "middle")

.attr("transform", "rotate(-90)")
.attr("pointer-events", "none")
.text("Reps");

var title =
axis_group
.append("text")
.attr("x", 740 / 2)
.attr("y", -80)
.style("font-size", 20)
.attr("text-anchor", "middle")
.style("text-decoration", "underline")
.text("Bench Press");

var sequence_title =
axis_group
.append("text")
.attr("x", 740 / 2)
.attr("y", -40)
.style("font-size", 30)
.attr("text-anchor", "middle")
.style("text-decoration", "none")
.attr("fill", "#aaabbb")
.text("Loading…");

var event_text =
axis_group
.append("text")
.attr("x", 740 / 2)
.attr("y", -10)
.style("font-size", 20)
.attr("text-anchor", "middle")
.style("text-decoration", "none")
.attr("fill", "#aaabff")
.text("");

const dominated_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("dominated_group", true);

var dominated_label_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("dominated_label_group", true);

dominated_label_group = dominated_group

const pareto_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("pareto_group", true);

const pareto_label_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("pareto_label_group", true);

const particle_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("particle_group", true);

var overlay_group = d3
.select("#plotapi-chart-ffb71dbc_svg")
.append("g")
.classed("overlay_group", true);

update_text();

var sequence_title_bbox = sequence_title.node().getBBox()

d3.select("#plotapi-chart-ffb71dbc_svg .overlay_group")
.append("svg:a")
.append("image")
.style("cursor", "pointer")
.attr("xlink:href", "https://plotapi.com/gallery/icon/continue.svg")
.attr("width", 30)
.attr("height", 30)
.attr("x", 20 + (740 / 2) + (sequence_title_bbox.width / 2))
.attr("y", -40 – 27)
.attr("opacity", 0.6)
.attr("id", "plotapi-chart-ffb71dbc_next");

d3.select("#plotapi-chart-ffb71dbc_next").on(
"click",
function (d, i)
d.stopPropagation();
clearTimeout(auto_proceed_timeout);
auto_proceed_interrupt = true;
next_order();

);

d3.select("#plotapi-chart-ffb71dbc_next").on(
"mouseover",
function (d, i)
if (sequence_index + 1 < dataset_collection.length)
d3.select("#plotapi-chart-ffb71dbc_next").attr("opacity", 1);

);

d3.select("#plotapi-chart-ffb71dbc_next").on(
"mouseout",
function (d, i)
if (sequence_index + 1 0)
d3.select(“#plotapi-chart-ffb71dbc_prev”).attr(“opacity”, 1);

);

d3.select(“#plotapi-chart-ffb71dbc_prev”).on(
“mouseout”,
function (d, i)
if (sequence_index > 0)
d3.select(“#plotapi-chart-ffb71dbc_prev”).attr(
“opacity”,
0.6
);

);

function next_order()
if (sequence_index + 1 = 0)
last_sequence_index = sequence_index;
sequence_index–;

if (range_x != pareto_range_x && range_y != pareto_range_y)
if (zoom_timeout == null)
zoomed_user = zoomed;

zoomed = false;
clearTimeout(zoom_timeout)
zoom_timeout = setTimeout(zoomUser, (500 * 4) + 500);

update_data(sequence_index)
update_axis()
update_text()
update_dominated_markers()
update_pareto_markers(false)

function init()
last_sequence_index = sequence_index;
n_final_ranks = Math.max.apply(Math, dataset_collection[dataset_collection.length – 1].map(function (o) return o.rank; )),

update_data(sequence_index)
update_axis()
update_text()
update_dominated_markers()
update_pareto_markers(false)

function update_navigation()

function update_text()
var date_string = sequence[sequence_index].toString();
var dt = new Date(date_string.substring(0, 4), date_string.substring(4, 6) – 1, date_string.substring(6, 8)).toDateString();

title.text(“Bench Press”);

sequence_title.text(dt);

if (sequence_index + 1 0)
d3.select(“#plotapi-chart-ffb71dbc_prev”)
.attr(“opacity”, “0.6”);

else
d3.select(“#plotapi-chart-ffb71dbc_prev”)
.attr(“opacity”, “0.1”);

var current_event = events.find(item =>
return item.order === sequence[sequence_index]
)

if (current_event == undefined)
event_text.attr(“opacity”, 0.5);

else
event_text
.attr(“opacity”, 1)
.text(current_event.event)

function update_marker_positions(population, population_label)
population.selectAll(‘circle’)
.transition()
.duration(500)
.attr(“cx”, function (d)
return x_scale(d.objv_1);
)
.attr(“cy”, function (d)
return y_scale(d.objv_2);
)

population_label.selectAll(‘text’)
.transition()
.duration(500)
.attr(“x”, function (d)
return x_scale(d.objv_1);
)
.attr(“y”, function (d)
return y_scale(d.objv_2);
)

function update_dominated_markers(next) {

update_marker_positions(dominated_group, dominated_label_group)

for (var index = 0; index < dominated_dataset.length; index++)
solution = dominated_dataset[index];

solution_marker = dominated_group.select(".solution_" + solution.objv_1 + "_" + solution.objv_2)
solution_label = dominated_label_group.select(".solution_label_" + solution.objv_1 + "_" + solution.objv_2)

if (solution_marker.empty())
solution_marker = dominated_group
.append("circle")
.datum(solution)
.attr("cx", function (d)
return x_scale(d.objv_1);
)
.attr("cy", function (d)
return y_scale(d.objv_2);
)
.attr("class", function (d)
return "solution_" + d.objv_1 + "_" + d.objv_2;
)
.attr("fill", function (d)
return color(d.rank);
)
.attr("stroke", function (d)
return darkenColor(color(d.rank), 0.5);
)
.attr("stroke-width", function (d)
return 2;
)
.attr("opacity", 0)
.on("mouseover", mouseoverNode())
.on("mouseout", mouseoutNode())

if (solution_label.empty())
solution_label = dominated_label_group
.append('text')
.datum(solution)
.attr("x", function (d)
return x_scale(d.objv_1);
)
.attr("y", function (d)
return y_scale(d.objv_2);
)
.attr("text-anchor", "middle")
.style("dominant-baseline", "middle")
.attr("class", function (d)
return "solution_label_" + d.objv_1 + "_" + d.objv_2;
)

.attr("opacity", 0)
.attr("font-size", 20 + "px")
.attr("fill", "#ffffff")
.attr("stroke", "#333333")
.attr("stroke-width", 3 + "px")
.attr("paint-order", "stroke")
.attr("stroke-linecap", "butt")
.attr("stroke-linejoin", "miter")
.text(solution.count)
.attr("pointer-events", "none");

if (solution_marker.datum()['current_sequence_index'] != sequence_index)
solution_marker.datum(solution)
solution_label.datum(solution)

solution_marker.datum()['current_sequence_index'] = sequence_index;
solution_label.datum()['current_sequence_index'] = sequence_index;

solution_marker
.transition()
.duration(500)
.attr("cx", function (d)
return x_scale(d.objv_1);
)
.attr("cy", function (d)
return y_scale(d.objv_2);
)
.attr("r", 20)
.style("fill", function (d)
return color(d.rank)

)
.attr("stroke", function (d)
return darkenColor(color(d.rank), 0.5);
);

solution_marker
.transition()
.delay(500)
.duration(500)

.attr("opacity", 1)

solution_label
.text(solution.count)
.on("mouseover", mouseoverNode())
.on("mouseout", mouseoutNode())

if (solution_label.datum()['last_count'] && solution_label.datum()['last_count'] != solution.count)

solution_label
.transition()
.duration(500)

.attr("font-size", (20 * 2) + "px")
.attr("x", function (d)
return x_scale(d.objv_1);
)
.attr("y", function (d)
return y_scale(d.objv_2);
)

else
solution_label
.transition()
.duration(500)
.attr("x", function (d)
return x_scale(d.objv_1);
)
.attr("y", function (d)
return y_scale(d.objv_2);
)

solution_label
.transition()
.delay(500)
.duration(500)

.attr("opacity", 1)
.attr("font-size", 20 + "px")

solution_label.datum()['last_count'] = solution.count;

dominated_group.selectAll('circle')
.filter(function (d) return d.current_sequence_index != sequence_index; )
.transition()
.delay(500 * 2)
.duration(500)

.attr("opacity", 0)
.on("end", function (d)
d.current_sequence_index = -1;
this.remove()
)

dominated_label_group.selectAll('text')
.filter(function (d) return d.current_sequence_index != sequence_index; )
.transition()
.delay(500 * 2)
.duration(500)

.attr("opacity", 0)
.on("end", function (d)
d.current_sequence_index = -1;
this.remove()
)

}

function firework(x, y)
for (var trail_index = 0; trail_index < 6; trail_index++)
for (var index = 0; index < 25; index++)
particle_group
.append("circle")
.style("fill", function (d)
return 'red'
)
.style('opacity', 1 – (trail_index * 0.15))
.attr('transform', 'translate(' + x + ',' + y + ')rotate(' + (360 / 25) * index + ')')
.attr("r", 5)
.transition()
.delay(trail_index * 20)
.ease(d3.easeCubicOut)
.duration(2500)
.attr("cy", 200 – (trail_index * 10))
.style("fill", function (d)
return 'transparent'
)
.on("end", function (d)
this.remove()
)

function update_pareto_markers(next)
update_marker_positions(pareto_group, pareto_label_group)

for (var index = 0; index
next_order()
, (500 * 2) + 500 + 2500);

}

function update_axis()

if (“min” == “max”)
limit_x_axis = [Math.max(…limit_x_axis), Math.min(…limit_x_axis)];

else
limit_x_axis = [Math.min(…limit_x_axis), Math.max(…limit_x_axis)];

if (“min” == “max”)
limit_y_axis = [Math.min(…limit_y_axis), Math.max(…limit_y_axis)];

else
limit_y_axis = [Math.max(…limit_y_axis), Math.min(…limit_y_axis)];

x_scale = d3
.scaleLinear()
.domain(limit_x_axis)
.range([0, 740]);

x_axis = d3
.axisBottom()
.scale(x_scale)
.ticks(650 / 100, undefined)
.tickSize(-650)
.tickFormat((d) => d3.format(“,”)(d));

x_axis_line
.transition()
.duration(500)
.call(x_axis);

y_scale = d3
.scaleLinear()
.domain(limit_y_axis)
.range([0, 650]);

y_axis = d3
.axisLeft()
.scale(y_scale)
.ticks(740 / 100, undefined)
.tickSize(-740)

y_axis.tickFormat(function (d)

return Number.isInteger(d) ? d : “”
)

y_axis_line
.transition()
.duration(500)
.call(y_axis);

function update_data(sequence_index)
dataset = dataset_collection[sequence_index]

dataset = dataset.filter(function (solution)
return solution.rank < 10;
);

dominated_dataset = dataset.filter(function (solution)
return solution.rank 0;
);

pareto_dataset = dataset.filter(function (solution)
return solution.rank == 0;
);

n_ranks = Math.max.apply(Math, dataset.map(function (o) return o.rank; )),

range_x = [Math.min.apply(Math, dataset.map(function (o) return o.objv_1; )), Math.max.apply(Math, dataset.map(function (o) return o.objv_1; ))]
range_y = [Math.min.apply(Math, dataset.map(function (o) return o.objv_2; )), Math.max.apply(Math, dataset.map(function (o) return o.objv_2; ))]

if(range_x[0] == range_x[1])
padding_x = range_x[0] / 10;

else
padding_x = (range_x[1] – range_x[0]) / 10;

if(range_y[0] == range_y[1])
padding_y = range_y[0] / 10;

else
padding_y = (range_y[1] – range_y[0]) / 10;

range_x[1] = range_x[1] + padding_x
range_x[0] = range_x[0] – padding_x

range_y[1] = range_y[1] + padding_y
range_y[0] = range_y[0] – padding_y

pareto_range_x = [Math.min.apply(Math, pareto_dataset.map(function (o) return o.objv_1; )), Math.max.apply(Math, pareto_dataset.map(function (o) return o.objv_1; ))]
pareto_range_y = [Math.min.apply(Math, pareto_dataset.map(function (o) return o.objv_2; )), Math.max.apply(Math, pareto_dataset.map(function (o) return o.objv_2; ))]

if(pareto_range_x[0] == pareto_range_x[1])
pareto_padding_x = pareto_range_x[0] / 10;

else
pareto_padding_x = (pareto_range_x[1] – pareto_range_x[0]) / 10;

if(pareto_range_y[0] == pareto_range_y[1])
pareto_padding_y = pareto_range_y[0] / 10;

else
pareto_padding_y = (pareto_range_y[1] – pareto_range_y[0]) / 10;

pareto_range_x[1] = pareto_range_x[1] + pareto_padding_x
pareto_range_x[0] = pareto_range_x[0] – pareto_padding_x

pareto_range_y[1] = pareto_range_y[1] + pareto_padding_y
pareto_range_y[0] = pareto_range_y[0] – pareto_padding_y

if (zoomed)
limit_y_axis = pareto_range_y;
limit_x_axis = pareto_range_x;

else
limit_y_axis = range_y;
limit_x_axis = range_x;

function mouseoverNode()

return function (d, i)
let size;
let population;
if (i.rank == 0)
population = pareto_group.selectAll(‘circle’);
size = 25;

else
population = dominated_group.selectAll(‘circle’);
size = 20;

population
.attr(“r”, function (d)

if (d.rank == i.rank)
return size * 1.25;

return size;
)
.attr(“stroke”, function (d)
if (d.rank == i.rank)

return ‘lightgreen’;

else if (d.rank == 0)
return darkenColor(color(d.rank), 0.5);

else

return darkenColor(color(d.rank), 0.5);

)
.attr(“stroke-width”, function (d)
if (d.rank == 0)
return 5;

else if (d.rank == i.rank)
return 5;

else
return 2;

)

d3.select(d.srcElement).attr(“r”, size * 1.25)

tippy_content = i.objv_1 + “” + ” kg” + “, ” + i.objv_2 + “” + ” reps” + “
Rank: ” + i.rank + “
Sets:” + i.count;
if (this._tippy == null)
tippy(this,
touch: ‘hold’,
allowHTML: true,
content: tippy_content,
arrow: true,
maxWidth: 300,
theme: ‘translucent’,
);

else
this._tippy.setContent(tippy_content);

function mouseoutNode()

return function (d, i)

let population;
let size;
if (i.rank == 0)
population = pareto_group.selectAll(‘circle’);
size = 25;

else
population = dominated_group.selectAll(‘circle’);
size = 20;

population
.attr(“r”, function (d)
if (d.rank == i.rank)
return size;

return size;
)
.attr(“stroke”, function (d)
return darkenColor(color(d.rank), 0.5);
if (d.rank == 0)
return ‘red’;

else
return ‘gray’; color(d.rank);

)
.attr(“stroke-width”, function (d)
if (d.rank == 0)
return 5;

else
return 2;

)

function zoom()
if (zoomed)
zoomed = false;
limit_x_axis = range_x;
limit_y_axis = range_y;

else
zoomed = true;
limit_x_axis = pareto_range_x;
limit_y_axis = pareto_range_y;

update_axis()
update_text()

update_marker_positions(dominated_group, dominated_label_group)
update_marker_positions(pareto_group, pareto_label_group)

function zoomOut()

zoomed = false;
limit_x_axis = range_x;
limit_y_axis = range_y;

update_axis()
update_text()

update_marker_positions(dominated_group, dominated_label_group)
update_marker_positions(pareto_group, pareto_label_group)

function zoomIn()

zoomed = true;
limit_x_axis = pareto_range_x;
limit_y_axis = pareto_range_y;

update_axis()
update_text()

update_marker_positions(dominated_group, dominated_label_group)
update_marker_positions(pareto_group, pareto_label_group)

function zoomUser()
zoom_timeout = null;
if (zoomed != zoomed_user)
zoomed = zoomed_user;
if (zoomed)

limit_x_axis = pareto_range_x;
limit_y_axis = pareto_range_y;

else
limit_x_axis = range_x;
limit_y_axis = range_y;

update_axis()
update_text()

update_marker_positions(dominated_group, dominated_label_group)
update_marker_positions(pareto_group, pareto_label_group)

d3.select(“#plotapi-chart-ffb71dbc”).on(“click”, zoom)

init();
}

}());

Here we can see the default behaviour of Plotapi Pareto Front.

You can do so much more than what’s presented in this example, and we’ll cover this in later sections. If you want to see the full list of growing features, check out the Plotapi Documentation.