From 31f0b64c44f4a108287a70d9a536f19dd34c8031 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 08:37:19 -0700 Subject: [PATCH 01/22] Refactor Geom class --- inst/htmljs/animint.js | 317 +++++++++++++++++++++++++++++++++++------ 1 file changed, 274 insertions(+), 43 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 8ff605999..4091e0e3e 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1840,54 +1840,285 @@ var animint = function (to_select, json_file) { } }; - - - var value_tostring = function(selected_values) { - //function that is helpful to change the format of the string - var selector_url="#" - for (var selc_var in selected_values){ - if(selected_values.hasOwnProperty(selc_var)){ - var values_str=selected_values[selc_var].join(); - var sub_url=selc_var.concat("=","{",values_str,"}"); - selector_url=selector_url.concat(sub_url); + // Refactor geom based on OOP, more specific geom can be extended from + // the basic Geom class + class Geom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + this.g_info = g_info; + this.chunk = chunk; + this.selector_name = selector_name; + this.PANEL = PANEL; + this.scales = {}; + + this.g_info.tr.select('td.status').text('displayed'); + + // SVG and scales + this.svg = SVGs[this.g_info.classsed]; + const g_names = this.g_info.classed.split('_'); + const p_name = g_names[g_names.length - 1]; + this.scales = Plots[p_name].scales[this.PANEL]; + + this.layer_g_element = this.svg.select('g.' + g_info.classed); + this.panel_g_element = this.layer_g_element.select('g.PANEL' + PANEL); + this.elements = this.panel_g_element.selectAll('.geom'); + + // selection features + this.has_clickSelects = this.g_info.aes.hasOwnProperty('clickSelects'); + this.has_clickSelects_variable = this.g_info.aes.hasOwnProperty( + 'clickSelects.variable' + ); + + this.selected_arrays = [[]]; + this.handle_subset_order(); + this.prepare_data(); + this.Selectors = Selectors; + + this.init_styles(g_info); + this.init_key_id(g_info); + this.init_select_style(); + } + + // Method to handle subset_order and selected_arrays + handle_subset_order() { + this.g_info.subset_order.forEach((aes_name) => { + let selected, values; + let new_arrays = []; + if (0 < aes_name.indexOf('.variable')) { + this.selected_arrays.forEach((old_array) => { + let some_data = this.chunk; + old_array.forEach((value) => { + if (some_data.hasOwnProperty(value)) { + some_data = some_data[value]; + } else { + some_data = {}; + } + }); + values = d3.keys(some_data); + values.forEach((s_name) => { + selected = this.Selectors[s_name].selected; + let new_array = old_array.concat(s_name).concat(selected); + new_arrays.push(new_array); + }); + }); + } else { + if (aes_name === 'PANEL') { + selected = this.PANEL; + } else { + const s_name = this.g_info.aes[aes_name]; + selected = this.Selectors[s_name].selected; } + values = Array.isArray(selected) ? selected : [selected]; + values.forEach((value) => { + this.selected_arrays.forEach((old_array) => { + let new_array = old_array.concat(value); + new_arrays.push(new_array); + }); + }); + } + this.selected_arrays = new_arrays; + }); + } + + // Method to prepare data for data-binding + prepare_data() { + let data; + if (this.g_info.data_is_object) { + data = {}; + } else { + data = []; } - var url_nohash=window.location.href.match(/(^[^#]*)/)[0]; - selector_url=url_nohash.concat(selector_url); - return selector_url; - }; - - var get_values=function(){ - // function that is useful to get the selected values - var selected_values={} - for(var s_name in Selectors){ - var s_info=Selectors[s_name]; - var initial_selections = []; - if(s_info.type==="single"){ - initial_selections=[s_info.selected]; - } - else{ - for(var i in s_info.selected) { - initial_selections[i] = s_info.selected[i]; + this.selected_arrays.forEach((value_array) => { + let some_data = this.chunk; + value_array.forEach((value) => { + if (some_data.hasOwnProperty(value)) { + some_data = some_data[value]; + } else { + if (this.g_info.data_is_object) { + some_data = {}; + } else { + some_data = []; + } } + }); + if (this.g_info.data_is_object) { + if (Array.isArray(some_data) && some_data.length) { + data['0'] = some_data; + } else { + for (let k in some_data) { + data[k] = some_data[k]; + } } - selected_values[s_name]=initial_selections; + } else { + data = data.concat(some_data); + } + }); + return data; + } + + // Initialize styles from g_info params + init_styles(g_info) { + this.base_opacity = g_info.params.hasOwnProperty('alpha') + ? g_info.params.alpha + : 1; + this.off_opacity = g_info.params.hasOwnProperty('alpha_off') + ? g_info.params.alpha_off + : this.base_opacity - 0.5; + + this.size = g_info.geom === 'text' ? 12 : 2; + this.size = g_info.params.hasOwnProperty('size') + ? g_info.params.size + : size; + + // stroke_width for geom_point + this.stroke_width = g_info.params.hasOwnProperty('stroke') + ? g_info.params.stroke + : 1; // by default ggplot2 has 0.5, animint has 1 + this.linetype = g_info.params.linetype || 'solid'; + this.colour = + g_info.geom === 'rect' && + has_clickSelects && + g_info.params.colour === 'transparent' + ? 'black' + : g_info.params.colour || 'black'; + this.fill = g_info.params.fill + ? g_info.params.fill + : g_info.params.colour || this.colour; + this.angle = g_info.params.hasOwnProperty('angle') + ? g_info.params['angle'] + : 0; + this.text_anchor = g_info.params.hasOwnProperty('anchor') + ? g_info.params['anchor'] + : 'middle'; + } + + get_alpha(d) { + return this.aes.hasOwnProperty('alpha') && d.hasOwnProperty('alpha') + ? d['alpha'] + : this.base_opacity; + } + + get_alpha_off(d) { + let a; + if ( + this.aes.hasOwnProperty('alpha_off') && + d.hasOwnProperty('alpha_off') + ) { + a = d['alpha_off']; + } else if (this.g_info.params.hasOwnProperty('alpha_off')) { + a = this.g_info.params.alpha_off; + } else if ( + this.aes.hasOwnProperty('alpha') && + d.hasOwnProperty('alpha') + ) { + a = d['alpha'] - 0.5; + } else { + a = this.off_opacity; } - return selected_values; - }; - - // var counter=-1; - // var update_selector_url = function() { - // var selected_values=get_values(); - // var url=value_tostring(selected_values); - // if(counter===-1){ - // $(".table_selector_widgets").after(""); - // $(".selectorurl").append("

Current URL

"); - // $(".selectorurl").append(""); - // counter++; - // } - // $(".selectorurl a").attr("href",url).text(url); - // }; + return a; + } + + get_size(d) { + return this.aes.hasOwnProperty('size') && d.hasOwnProperty('size') + ? d['size'] + : this.size; + } + + get_stroke_width(d) { + return this.aes.hasOwnProperty('stroke') && d.hasOwnProperty('stroke') + ? d['stroke'] + : this.stroke_width; + } + + get_dasharray(d) { + let lt = this.linetype; + if (this.aes.hasOwnProperty('linetype') && d.hasOwnProperty('linetype')) { + lt = d['linetype']; + } + return this.linetypesize2dasharray(lt, this.get_size(d)); + } + + get_angle(d) { + let x = this.scales['x'](d['x']); + let y = this.scales['y'](d['y']); + let angle = d.hasOwnProperty('angle') ? d['angle'] : this.angle; + return `rotate(${-angle}, ${x}, ${y})`; + } + + get_colour(d) { + return d.hasOwnProperty('colour') ? d['colour'] : this.colour; + } + + get_colour_off(d) { + if ( + this.aes.hasOwnProperty('colour_off') && + d.hasOwnProperty('colour_off') + ) { + return d['colour_off']; + } else if (this.g_info.params.hasOwnProperty('colour_off')) { + return this.g_info.params.colour_off; + } + return null; // No default `colour_off` value + } + + get_fill(d) { + return d.hasOwnProperty('fill') ? d['fill'] : this.fill; + } + + get_fill_off(d) { + if (this.aes.hasOwnProperty('fill_off') && d.hasOwnProperty('fill_off')) { + return d['fill_off']; + } else if (this.g_info.params.hasOwnProperty('fill_off')) { + return this.g_info.params.fill_off; + } + return null; // No default `fill_off` value + } + + get_text_anchor(d) { + if (this.text_anchor) { + return this.text_anchor; + } else if (d.hasOwnProperty('anchor')) { + return d['anchor']; + } + return 'middle'; + } + + // initialize key and id functions based on g_info + init_key_id(g_info) { + this.id_fun = (d) => { + return d.id; + }; + + if (g_info.aes.hasOwnProperty('key')) { + this.key_fun = (d) => { + return d.key; + }; + } else { + this.key_fun = null; + } + } + + // Initialize the selection style function + init_select_style() { + this.select_style_fun = (g_info, e) => { + if (!g_info.select_style.includes('stroke')) { + e.style('stroke', this.get_colour); + } + if (!g_info.select_style.includes('opacity')) { + e.style('opacity', this.get_alpha); + } + if (!g_info.select_style.includes('fill')) { + e.style('fill', this.get_fill); + } + }; + } + + // Utility function to return scale-aesthetic function + toXY(xy, a) { + return (d) => { + return this.scales[xy](d[a]); + }; + } + } // update scales for the plots that have update_axes option in // theme_animint From f23980db538c20a77be0623244f85194d158b2c3 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 21:20:07 -0700 Subject: [PATCH 02/22] prepare data for geom line, path, ribbon and polygon --- inst/htmljs/animint.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 4091e0e3e..67dd8decb 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1870,12 +1870,21 @@ var animint = function (to_select, json_file) { this.selected_arrays = [[]]; this.handle_subset_order(); - this.prepare_data(); + this.data = this.prepare_data(); this.Selectors = Selectors; this.init_styles(g_info); this.init_key_id(g_info); this.init_select_style(); + + if (this.g_info.data_is_object) { + this.prepare_special_data(); + this.define_line_type(); + this.define_element_actions(); + this.define_link_actions(); + } else { + this.define_simple_link_actions(); + } } // Method to handle subset_order and selected_arrays @@ -2112,6 +2121,26 @@ var animint = function (to_select, json_file) { }; } + prepare_special_data() { + let keyed_data = {}, one_group, group_id, k; + for (group_id in this.data) { + one_group = this.data[group_id]; + one_row = one_group[0]; + if (one_row.hasOwnProperty('key')) { + k = one_row.key; + } else { + k = group_id; + } + keyed_data[k] = one_group; + } + + let kv_array = d3.entries(d3.keys(keyed_data)); + this.kv = kv_array.map(function (d) { + d.clickSelects = keyed_data[d.value][0].clickSelects; + return d; + }) + } + // Utility function to return scale-aesthetic function toXY(xy, a) { return (d) => { From 7309f5d48d49f38ce1bdc901098dbf0aea422a98 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 21:24:02 -0700 Subject: [PATCH 03/22] geom_ribbon has a different linetype --- inst/htmljs/animint.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 67dd8decb..c3f93b9e9 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -2141,6 +2141,19 @@ var animint = function (to_select, json_file) { }) } + define_line_type() { + if (this.g_info.geom === 'ribbon') { + this.lineThing = d3.svg.area() + .x(this.toXY('x', 'x')) + .y(this.toXY('y', 'ymax')) + .y0(this.toXY('y', 'ymin')); + } else { + this.lineThing = d3.svg.line() + .x(this.toXY('x', 'x')) + .y(this.toXY('y', 'y')); + } + } + // Utility function to return scale-aesthetic function toXY(xy, a) { return (d) => { From d7e36f956cd79f01c32291a4b95e9f4cc5a2fe7d Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 22:05:28 -0700 Subject: [PATCH 04/22] set up the special D3 data binding --- inst/htmljs/animint.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index c3f93b9e9..c84949763 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1880,6 +1880,7 @@ var animint = function (to_select, json_file) { if (this.g_info.data_is_object) { this.prepare_special_data(); this.define_line_type(); + this.setup_data_binding(); this.define_element_actions(); this.define_link_actions(); } else { @@ -2141,6 +2142,7 @@ var animint = function (to_select, json_file) { }) } + // TODO: Move this to a geom_ribbon subclass method later. define_line_type() { if (this.g_info.geom === 'ribbon') { this.lineThing = d3.svg.area() @@ -2154,6 +2156,21 @@ var animint = function (to_select, json_file) { } } + // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. + setup_data_binding() { + this.key_fun = function(group_info) { + return group_info.value; + }; + + this.id_fun = function(group_info) { + var one_group = this.keyed_data[group_info.value]; + var one_row = one_group[0]; + return one_row.id; + }.bind(this); + + this.elements = this.elements.data(this.kv, this.key_fun); + } + // Utility function to return scale-aesthetic function toXY(xy, a) { return (d) => { From e693d59a794a6a07e4adb43f8961eec3adc2524a Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 22:15:13 -0700 Subject: [PATCH 05/22] update each individual graphical element --- inst/htmljs/animint.js | 69 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index c84949763..e7de8a615 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -2123,7 +2123,10 @@ var animint = function (to_select, json_file) { } prepare_special_data() { - let keyed_data = {}, one_group, group_id, k; + let keyed_data = {}, + one_group, + group_id, + k; for (group_id in this.data) { one_group = this.data[group_id]; one_row = one_group[0]; @@ -2139,18 +2142,20 @@ var animint = function (to_select, json_file) { this.kv = kv_array.map(function (d) { d.clickSelects = keyed_data[d.value][0].clickSelects; return d; - }) + }); } // TODO: Move this to a geom_ribbon subclass method later. define_line_type() { if (this.g_info.geom === 'ribbon') { - this.lineThing = d3.svg.area() + this.lineThing = d3.svg + .area() .x(this.toXY('x', 'x')) .y(this.toXY('y', 'ymax')) .y0(this.toXY('y', 'ymin')); } else { - this.lineThing = d3.svg.line() + this.lineThing = d3.svg + .line() .x(this.toXY('x', 'x')) .y(this.toXY('y', 'y')); } @@ -2158,19 +2163,67 @@ var animint = function (to_select, json_file) { // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. setup_data_binding() { - this.key_fun = function(group_info) { + this.key_fun = function (group_info) { return group_info.value; }; - - this.id_fun = function(group_info) { + + this.id_fun = function (group_info) { var one_group = this.keyed_data[group_info.value]; var one_row = one_group[0]; return one_row.id; }.bind(this); - + this.elements = this.elements.data(this.kv, this.key_fun); } + // update each individual graphical element('e') + define_element_actions() { + this.eActions = (e) => { + const getGroupAndRow = (group_info) => { + const one_group = this.keyed_data[group_info.value]; + const one_row = one_group[0]; + return { one_group, one_row }; + }; + + e.attr('d', (d) => { + const { one_group } = getGroupAndRow(d); + const no_na = one_group.filter((d) => { + if (this.g_info.geom === 'ribbon') { + return !isNaN(d.x) && !isNaN(d.ymin) && !isNaN(d.ymax); + } + return !isNaN(d.x) && !isNaN(d.y); + }); + return this.lineThing(no_na); + }); + + const styleSetter = (group_info) => { + const { one_row } = getGroupAndRow(group_info); + return { + fill: this.g_info.geom === 'line' || this.g_info.geom === 'path' ? 'none' : this.get_fill(one_row), + strokeWidth: this.get_size(one_row), + stroke: this.get_colour(one_row), + strokeDasharray: this.get_dasharray(one_row), + }; + }; + + e.each(function (group_info) { + const styles = styleSetter(group_info); + d3.select(this) + .style('fill', styles.fill) + .style('stroke-width', styles.strokeWidth) + .style('stroke', styles.stroke) + .style('stroke-dasharray', styles.strokeDasharray); + }); + + if (!this.g_info.select_style.includes('opacity')) { + e.style('opacity', (group_info) => { + const { one_row } = getGroupAndRow(group_info); + return this.get_alpha(one_row); + }); + } + }; + } + // Utility function to return scale-aesthetic function toXY(xy, a) { return (d) => { From 64a737ed5a5db0549b50bd299fdc5c30559057fe Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 22:23:21 -0700 Subject: [PATCH 06/22] add linkActions logic --- inst/htmljs/animint.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index e7de8a615..4af445957 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -2224,6 +2224,30 @@ var animint = function (to_select, json_file) { }; } + define_link_actions() { + this.linkActions = (a_elements) => { + a_elements + .attr('xlink:href', (group_info) => { + const one_group = this.keyed_data[group_info.value]; + const one_row = one_group[0]; + return one_row.href; + }) + .attr('target', '_blank') + .attr('class', 'geom'); + }; + } + + define_simple_link_actions() { + this.linkActions = (a_elements) => { + a_elements + .attr('xlink:href', (d) => { + return d.href; + }) + .attr('target', '_blank') + .attr('class', 'geom'); + }; + } + // Utility function to return scale-aesthetic function toXY(xy, a) { return (d) => { From 6e6237c24798ab41a36c1f8e4ed4f9a087109213 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Mon, 9 Oct 2023 22:25:28 -0700 Subject: [PATCH 07/22] delete the old 'draw_geom' function --- inst/htmljs/animint.js | 873 ----------------------------------------- 1 file changed, 873 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 4af445957..63949f27b 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -966,879 +966,6 @@ var animint = function (to_select, json_file) { }); }); }//download_chunk. - - // update_geom is responsible for obtaining a chunk of downloaded - // data, and then calling draw_geom to actually draw it. - var draw_geom = function(g_info, chunk, selector_name, PANEL){ - g_info.tr.select("td.status").text("displayed"); - var svg = SVGs[g_info.classed]; - // derive the plot name from the geometry name - var g_names = g_info.classed.split("_"); - var p_name = g_names[g_names.length - 1]; - var scales = Plots[p_name].scales[PANEL]; - var selected_arrays = [ [] ]; //double array necessary. - var has_clickSelects = g_info.aes.hasOwnProperty("clickSelects"); - var has_clickSelects_variable = - g_info.aes.hasOwnProperty("clickSelects.variable"); - g_info.subset_order.forEach(function (aes_name) { - var selected, values; - var new_arrays = []; - if(0 < aes_name.indexOf(".variable")){ - selected_arrays.forEach(function(old_array){ - var some_data = chunk; - old_array.forEach(function(value){ - if(some_data.hasOwnProperty(value)) { - some_data = some_data[value]; - } else { - some_data = {}; - } - }) - values = d3.keys(some_data); - values.forEach(function(s_name){ - var selected = Selectors[s_name].selected; - var new_array = old_array.concat(s_name).concat(selected); - new_arrays.push(new_array); - }) - }) - }else{//not .variable aes: - if(aes_name == "PANEL"){ - selected = PANEL; - }else{ - var s_name = g_info.aes[aes_name]; - selected = Selectors[s_name].selected; - } - if(isArray(selected)){ - values = selected; //multiple selection. - }else{ - values = [selected]; //single selection. - } - values.forEach(function(value){ - selected_arrays.forEach(function(old_array){ - var new_array = old_array.concat(value); - new_arrays.push(new_array); - }) - }) - } - selected_arrays = new_arrays; - }); - // data can be either an array[] if it will be directly involved - // in a data-bind, or an object{} if it will be involved in a - // data-bind by group (e.g. geom_line). - var data; - if(g_info.data_is_object){ - data = {}; - }else{ - data = []; - } - selected_arrays.forEach(function(value_array){ - var some_data = chunk; - value_array.forEach(function(value){ - if (some_data.hasOwnProperty(value)) { - some_data = some_data[value]; - } else { - if(g_info.data_is_object){ - some_data = {}; - }else{ - some_data = []; - } - } - }); - if(g_info.data_is_object){ - if(isArray(some_data) && some_data.length){ - data["0"] = some_data; - }else{ - for(k in some_data){ - data[k] = some_data[k]; - } - } - }else{//some_data is an array. - data = data.concat(some_data); - } - }); - var aes = g_info.aes; - var toXY = function (xy, a) { - return function (d) { - return scales[xy](d[a]); - }; - }; - var layer_g_element = svg.select("g." + g_info.classed); - var panel_g_element = layer_g_element.select("g.PANEL" + PANEL); - var elements = panel_g_element.selectAll(".geom"); - // TODO: standardize this code across aes/styles. - let base_opacity; - let off_opacity; - // Explicitly check if it has the property, allows 0 as valid value - if (g_info.params.hasOwnProperty("alpha")) { - base_opacity = g_info.params.alpha; - } else { - base_opacity = 1; - } - if (g_info.params.hasOwnProperty("alpha_off")) { - off_opacity = g_info.params.alpha_off; - } else { - off_opacity = base_opacity - 0.5; - } - //alert(g_info.classed+" "+base_opacity); - var get_alpha = function (d) { - var a; - if (aes.hasOwnProperty("alpha") && d.hasOwnProperty("alpha")) { - a = d["alpha"]; - } else { - a = base_opacity; - } - return a; - }; - const get_alpha_off = function (d) { - let a; - if (aes.hasOwnProperty("alpha_off") && d.hasOwnProperty("alpha_off")) { - a = d["alpha_off"]; - } else if (g_info.params.hasOwnProperty("alpha_off")) { - a = g_info.params.alpha_off; - } else if (aes.hasOwnProperty("alpha") && d.hasOwnProperty("alpha")) { - a = d["alpha"] - 0.5; - } else { - a = off_opacity; - } - return a; - }; - var size = 2; - if(g_info.geom == "text"){ - size = 12; - } - if (g_info.params.hasOwnProperty("size")) { - size = g_info.params.size; - } - var get_size = function (d) { - if (aes.hasOwnProperty("size") && d.hasOwnProperty("size")) { - return d["size"]; - } - return size; - }; - - // stroke_width for geom_point - var stroke_width = 1; // by default ggplot2 has 0.5, animint has 1 - if (g_info.params.hasOwnProperty("stroke")) { - stroke_width = g_info.params.stroke; - } - var get_stroke_width = function (d) { - if (aes.hasOwnProperty("stroke") && d.hasOwnProperty("stroke")) { - return d["stroke"]; - } - return stroke_width; - } - - var linetype = "solid"; - if (g_info.params.linetype) { - linetype = g_info.params.linetype; - } - - var get_dasharray = function (d) { - var lt = linetype; - if (aes.hasOwnProperty("linetype") && d.hasOwnProperty("linetype")) { - lt = d["linetype"]; - } - return linetypesize2dasharray(lt, get_size(d)); - }; - var colour = "black"; - var fill = "black"; - let angle = 0; - if (g_info.params.hasOwnProperty("angle")) { - angle = g_info.params["angle"]; - } - const get_angle = function(d) { - // x and y are the coordinates to rotate around, we choose the center - // point of the text because otherwise it will rotate around (0,0) of its - // coordinate system, which is the top left of the plot - x = scales["x"](d["x"]); - y = scales["y"](d["y"]); - if (d.hasOwnProperty("angle")) { - angle = d["angle"]; - } - // ggplot expects angles to be in degrees CCW, SVG uses degrees CW, so - // we negate the angle. - return `rotate(${-angle}, ${x}, ${y})`; - }; - var get_colour = function (d) { - if (d.hasOwnProperty("colour")) { - return d["colour"] - } - return colour; - }; - if (g_info.geom == "rect" && has_clickSelects && g_info.params.colour == "transparent"){ - colour = "black"; - } else if(g_info.params.colour){ - colour = g_info.params.colour; - } - - // Only "colour_off" params appears would call this function, - // so no default off_colour value - const get_colour_off = function (d) { - let off_colour; - if (aes.hasOwnProperty("colour_off") && d.hasOwnProperty("colour_off")) { - off_colour = d["colour_off"]; - } else if(g_info.params.hasOwnProperty("colour_off")){ - off_colour = g_info.params.colour_off; - } - return off_colour; - }; - - var get_fill = function (d) { - if (d.hasOwnProperty("fill")) { - return d["fill"]; - } - return fill; - }; - if (g_info.params.fill) { - fill = g_info.params.fill; - }else if(g_info.params.colour){ - fill = g_info.params.colour; - } - - const get_fill_off = function (d) { - let off_fill; - if (aes.hasOwnProperty("fill_off") && d.hasOwnProperty("fill_off")) { - off_fill = d["fill_off"]; - } else if (g_info.params.hasOwnProperty("fill_off")) { - off_fill = g_info.params.fill_off; - } - return off_fill; - }; - - // For aes(hjust) the compiler should make an "anchor" column. - var text_anchor = "middle"; - if(g_info.params.hasOwnProperty("anchor")){ - text_anchor = g_info.params["anchor"]; - } - var get_text_anchor; - if(g_info.aes.hasOwnProperty("hjust")) { - get_text_anchor = function(d){ - return d["anchor"]; - } - }else{ - get_text_anchor = function(d){ - return text_anchor; - } - } - - var eActions, eAppend, linkActions; - var key_fun = null; - var id_fun = function(d){ - return d.id; - }; - if(g_info.aes.hasOwnProperty("key")){ - key_fun = function(d){ - return d.key; - }; - } - - // Apply user-configurable selection style into each geom later. - var select_style_fun = function(g_info, e){ - if(!g_info.select_style.includes("stroke")){ - e.style("stroke", get_colour); - } - if(!g_info.select_style.includes("opacity")){ - e.style("opacity", get_alpha); - } - if(!g_info.select_style.includes("fill")){ - e.style("fill", get_fill); - } - }; - if(g_info.data_is_object) { - - // Lines, paths, polygons, and ribbons are a bit special. For - // every unique value of the group variable, we take the - // corresponding data rows and make 1 path. The tricky part is - // that to use d3 I do a data-bind of some "fake" data which are - // just group ids, which is the kv variable in the code below - - // // case of only 1 line and no groups. - // if(!aes.hasOwnProperty("group")){ - // kv = [{"key":0,"value":0}]; - // data = {0:data}; - // }else{ - // // we need to use a path for each group. - // var kv = d3.entries(d3.keys(data)); - // kv = kv.map(function(d){ - // d[aes.group] = d.value; - // return d; - // }); - // } - - // For an example consider breakpointError$error which is - // defined using this R code - - // geom_line(aes(segments, error, group=bases.per.probe, - // clickSelects=bases.per.probe), data=only.error, lwd=4) - - // Inside update_geom the variables take the following values - // (pseudo-Javascript code) - - // var kv = [{"key":"0","value":"133","bases.per.probe":"133"}, - // {"key":"1","value":"2667","bases.per.probe":"2667"}]; - // var data = {"133":[array of 20 points used to draw the line for group 133], - // "2667":[array of 20 points used to draw the line for group 2667]}; - - // I do elements.data(kv) so that when I set the d attribute of - // each path, I need to select the correct group before - // returning anything. - - // e.attr("d",function(group_info){ - // var one_group = data[group_info.value]; - // return lineThing(one_group); - // }) - - // To make color work I think you just have to select the group - // and take the color of the first element, e.g. - - // .style("stroke",function(group_info){ - // var one_group = data[group_info.value]; - // var one_row = one_group[0]; - // return get_color(one_row); - // } - - // In order to get d3 lines to play nice, bind fake "data" (group - // id's) -- the kv variable. Then each separate object is plotted - // using path (case of only 1 thing and no groups). - - // we need to use a path for each group. - var keyed_data = {}, one_group, group_id, k; - for(group_id in data){ - one_group = data[group_id]; - one_row = one_group[0]; - if(one_row.hasOwnProperty("key")){ - k = one_row.key; - }else{ - k = group_id; - } - keyed_data[k] = one_group; - } - var kv_array = d3.entries(d3.keys(keyed_data)); - var kv = kv_array.map(function (d) { - //d[aes.group] = d.value; - - // Need to store the clickSelects value that will - // be passed to the selector when we click on this - // item. - d.clickSelects = keyed_data[d.value][0].clickSelects; - return d; - }); - - // line, path, and polygon use d3.svg.line(), - // ribbon uses d3.svg.area() - // we have to define lineThing accordingly. - if (g_info.geom == "ribbon") { - var lineThing = d3.svg.area() - .x(toXY("x", "x")) - .y(toXY("y", "ymax")) - .y0(toXY("y", "ymin")); - } else { - var lineThing = d3.svg.line() - .x(toXY("x", "x")) - .y(toXY("y", "y")); - } - // select the correct group before returning anything. - key_fun = function(group_info){ - return group_info.value; - }; - id_fun = function(group_info){ - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take key from first value in the group. - return one_row.id; - }; - elements = elements.data(kv, key_fun); - linkActions = function(a_elements){ - a_elements - .attr("xlink:href", function(group_info){ - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - return one_row.href; - }) - .attr("target", "_blank") - .attr("class", "geom") - ; - }; - eActions = function (e) { - e.attr("d", function (d) { - var one_group = keyed_data[d.value]; - // filter NaN since they make the whole line disappear! - var no_na = one_group.filter(function(d){ - if(g_info.geom == "ribbon"){ - return !isNaN(d.x) && !isNaN(d.ymin) && !isNaN(d.ymax); - }else{ - return !isNaN(d.x) && !isNaN(d.y); - } - }); - return lineThing(no_na); - }) - .style("fill", function (group_info) { - if (g_info.geom == "line" || g_info.geom == "path") { - return "none"; - } - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take color for first value in the group - return get_fill(one_row); - }) - .style("stroke-width", function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take size for first value in the group - return get_size(one_row); - }) - .style("stroke", function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take color for first value in the group - // Since line/path geom are using group to draw, - // so it is different from other geom - // and cannot call select_style_fun function here - if ((has_clickSelects || has_clickSelects_variable) && g_info.select_style.includes("stroke")){ - const v_name = g_info.aes['clickSelects.variable'] || g_info.aes['clickSelects']; - const s_info = Selectors[v_name]; - if(s_info.selected == one_row.clickSelects){ - return get_colour(one_row); - } else{ - return get_colour_off(one_row); - }; - }; - return get_colour(one_row); - }) - .style("stroke-dasharray", function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take linetype for first value in the group - return get_dasharray(one_row); - }) - .style("stroke-width", function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take line size for first value in the group - return get_size(one_row); - }); - if(!g_info.select_style.includes("opacity")){ - e.style("opacity", function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take line size for first value in the group - return get_alpha(one_row); - }) - } - }; - eAppend = "path"; - }else{ - linkActions = function(a_elements){ - a_elements.attr("xlink:href", function(d){ return d.href; }) - .attr("target", "_blank") - .attr("class", "geom"); - }; - } - if (g_info.geom == "segment") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("x1", function (d) { - return scales.x(d["x"]); - }) - .attr("x2", function (d) { - return scales.x(d["xend"]); - }) - .attr("y1", function (d) { - return scales.y(d["y"]); - }) - .attr("y2", function (d) { - return scales.y(d["yend"]); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - eAppend = "line"; - } - if (g_info.geom == "linerange") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("x1", function (d) { - return scales.x(d["x"]); - }) - .attr("x2", function (d) { - return scales.x(d["x"]); - }) - .attr("y1", function (d) { - return scales.y(d["ymax"]); - }) - .attr("y2", function (d) { - return scales.y(d["ymin"]); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - eAppend = "line"; - } - if (g_info.geom == "vline") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("x1", toXY("x", "xintercept")) - .attr("x2", toXY("x", "xintercept")) - .attr("y1", scales.y.range()[0]) - .attr("y2", scales.y.range()[1]) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - eAppend = "line"; - } - if (g_info.geom == "hline") { - // pretty much a copy of geom_vline with obvious modifications - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("y1", toXY("y", "yintercept")) - .attr("y2", toXY("y", "yintercept")) - .attr("x1", scales.x.range()[0]) - .attr("x2", scales.x.range()[1]) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - eAppend = "line"; - } - if (g_info.geom == "text") { - elements = elements.data(data, key_fun); - // TODO: how to support vjust? firefox doensn't support - // baseline-shift... use paths? - // http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Text - eActions = function (e) { - e.attr("x", toXY("x", "x")) - .attr("y", toXY("y", "y")) - .attr("font-size", get_size) - .style("text-anchor", get_text_anchor) - .attr("transform", get_angle) - .text(function (d) { - return d.label; - }); - }; - eAppend = "text"; - } - if (g_info.geom == "point") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("cx", toXY("x", "x")) - .attr("cy", toXY("y", "y")) - .attr("r", get_size) - .style("stroke-width", get_stroke_width); - select_style_fun(g_info, e); - }; - eAppend = "circle"; - } - if (g_info.geom == "tallrect") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("x", toXY("x", "xmin")) - .attr("width", function (d) { - return scales.x(d["xmax"]) - scales.x(d["xmin"]); - }) - .attr("y", scales.y.range()[1]) - .attr("height", scales.y.range()[0] - scales.y.range()[1]) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - eAppend = "rect"; - } - if (g_info.geom == "widerect") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("y", toXY("y", "ymax")) - .attr("height", function (d) { - return scales.y(d["ymin"]) - scales.y(d["ymax"]); - }) - .attr("x", scales.x.range()[0]) - .attr("width", scales.x.range()[1] - scales.x.range()[0]) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - eAppend = "rect"; - } - // geom_rect/geom_tile selection style logic: - // 1. in geom-tile.R we specify if the colour parameter, not aes, is null - // - it shall be transparent when there is no clickSelects - // - it is black when clickSelects is specified, and no params colour - // 2. When colour param is not null, whether it has clickSelects or not - // the colour/stroke is the RGB value of colour params - if (g_info.geom == "rect") { - elements = elements.data(data, key_fun); - eActions = function (e) { - e.attr("x", toXY("x", "xmin")) - .attr("width", function (d) { - return Math.abs(scales.x(d.xmax) - scales.x(d.xmin)); - }) - .attr("y", toXY("y", "ymax")) - .attr("height", function (d) { - return Math.abs(scales.y(d.ymin) - scales.y(d.ymax)); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size) - select_style_fun(g_info, e); - }; - eAppend = "rect"; - } - if (g_info.geom == "boxplot") { - - // TODO: currently boxplots are unsupported (we intentionally - // stop with an error in the R code). The reason why is that - // boxplots are drawn using multiple geoms and it is not - // straightforward to deal with that using our current JS - // code. After all, a boxplot could be produced by combing 3 - // other geoms (rects, lines, and points) if you really wanted - // it. - - fill = "white"; - - elements = elements.data(data); - eActions = function (e) { - e.append("line") - .attr("x1", function (d) { - return scales.x(d["x"]); - }) - .attr("x2", function (d) { - return scales.x(d["x"]); - }) - .attr("y1", function (d) { - return scales.y(d["ymin"]); - }) - .attr("y2", function (d) { - return scales.y(d["lower"]); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - e.append("line") - .attr("x1", function (d) { - return scales.x(d["x"]); - }) - .attr("x2", function (d) { - return scales.x(d["x"]); - }) - .attr("y1", function (d) { - return scales.y(d["upper"]); - }) - .attr("y2", function (d) { - return scales.y(d["ymax"]); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - e.append("rect") - .attr("x", function (d) { - return scales.x(d["xmin"]); - }) - .attr("width", function (d) { - return scales.x(d["xmax"]) - scales.x(d["xmin"]); - }) - .attr("y", function (d) { - return scales.y(d["upper"]); - }) - .attr("height", function (d) { - return Math.abs(scales.y(d["upper"]) - scales.y(d["lower"])); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size) - select_style_fun(g_info, e); - e.append("line") - .attr("x1", function (d) { - return scales.x(d["xmin"]); - }) - .attr("x2", function (d) { - return scales.x(d["xmax"]); - }) - .attr("y1", function (d) { - return scales.y(d["middle"]); - }) - .attr("y2", function (d) { - return scales.y(d["middle"]); - }) - .style("stroke-dasharray", get_dasharray) - .style("stroke-width", get_size); - select_style_fun(g_info, e); - }; - } - elements.exit().remove(); - var enter = elements.enter(); - if(g_info.aes.hasOwnProperty("href")){ - enter = enter.append("svg:a") - .append("svg:"+eAppend); - }else{ - enter = enter.append(eAppend) - .attr("class", "geom"); - } - if (has_clickSelects || has_clickSelects_variable) { - var selected_funs = function(style_name, select_fun){ - style_on_funs = { - "opacity": get_alpha, - "stroke": get_colour, - "fill": get_fill - }; - style_off_funs = { - "opacity": get_alpha_off, - "stroke": get_colour_off, - "fill": get_fill_off - }; - if(select_fun == "mouseout"){ - return function (d) { - var select_on = style_on_funs[style_name](d); - var select_off = style_off_funs[style_name](d); - if(has_clickSelects){ - return ifSelectedElse(d.clickSelects, g_info.aes.clickSelects, - select_on, select_off); - }else if(has_clickSelects_variable){ - return ifSelectedElse(d["clickSelects.value"], - d["clickSelects.variable"], - select_on, select_off); - } - } - } else if(select_fun == "mouseover"){ - return function (d) { - return style_on_funs[style_name](d); - } - }; - }; //selected_funs. - // My original design for clicking/interactivity/transparency: - // Basically I wanted a really simple way to show which element - // in a group of clickable geom elements is currently - // selected. So I decided that all the non-selected elements - // should have alpha transparency 0.5 less than normal, and the - // selected element should have normal alpha transparency. Also, - // the element currently under the mouse has normal alpha - // transparency, to visually indicate that it can be - // clicked. Looking at my examples, you will see that I - // basically use this in two ways: - - // 1. By specifying - // geom_vline(aes(clickSelects=variable),alpha=0.5), which - // implies a normal alpha transparency of 0.5. So all the vlines - // are hidden (normal alpha 0.5 - 0.5 = 0), except the current - // selection and the current element under the mouse pointer are - // drawn a bit faded with alpha=0.5. - - // 2. By specifying e.g. geom_point(aes(clickSelects=variable)), - // that implies a normal alpha=1. Thus the current selection and - // the current element under the mouse pointer are fully drawn - // with alpha=1 and the others are shown but a bit faded with - // alpha=0.5 (normal alpha 1 - 0.5 = 0.5). - - // Edit 19 March 2014: Now there are two styles to show the - // selection, depending on the geom. For most geoms it is as - // described above. But for geoms like rects with - // aes(fill=numericVariable), using opacity to indicate the - // selection results in a misleading decoding of the fill - // variable. So in this case we set stroke to "black" for the - // current selection. - - // TODO: user-configurable selection styles. - - var over_fun = function(e){ - g_info.select_style.forEach(function(s){ - e.style(s, selected_funs(s, "mouseover")); - }) - }; - var out_fun = function(e){ - g_info.select_style.forEach(function(s){ - e.style(s, selected_funs(s, "mouseout")); - }) - }; - elements.call(out_fun) - .on("mouseover", function (d) { - d3.select(this).call(over_fun); - }) - .on("mouseout", function (d) { - d3.select(this).call(out_fun); - }) - ; - if(has_clickSelects){ - elements.on("click", function (d) { - // The main idea of how clickSelects works: when we click - // something, we call update_selector with the clicked - // value. - var s_name = g_info.aes.clickSelects; - update_selector(s_name, d.clickSelects); - }); - }else{ - elements.on("click", function(d){ - var s_name = d["clickSelects.variable"]; - var s_value = d["clickSelects.value"]; - update_selector(s_name, s_value); - }); - } - }else{//has neither clickSelects nor clickSelects.variable. - elements.style("opacity", get_alpha); - // geom_segment/linerange/hline/vline no `stroke` with no clickSelects - const excludedGeoms = ["segment", "linerange", "hline", "vline"]; - if (!excludedGeoms.includes(g_info.geom)) { - elements.style("fill", get_fill); - } - if(g_info.geom != "text"){ // geom_text no `stroke` with no clickSelects - elements.style("stroke", get_colour); - } - } - var has_tooltip = g_info.aes.hasOwnProperty("tooltip"); - if(has_clickSelects || has_tooltip || has_clickSelects_variable){ - var text_fun, get_one; - if(g_info.data_is_object){ - get_one = function(d_or_kv){ - var one_group = keyed_data[d_or_kv.value]; - return one_group[0]; - }; - }else{ - get_one = function(d_or_kv){ - return d_or_kv; - }; - } - if(has_tooltip){ - text_fun = function(d){ - return d.tooltip; - }; - }else if(has_clickSelects){ - text_fun = function(d){ - var v_name = g_info.aes.clickSelects; - return v_name + " " + d.clickSelects; - }; - }else{ //clickSelects_variable - text_fun = function(d){ - return d["clickSelects.variable"] + " " + d["clickSelects.value"]; - }; - } - // if elements have an existing title, remove it. - elements.selectAll("title").remove(); - elements.append("svg:title") - .text(function(d_or_kv){ - var d = get_one(d_or_kv); - return text_fun(d); - }) - ; - } - // Set attributes of only the entering elements. This is needed to - // prevent things from flying around from the upper left when they - // enter the plot. - eActions(enter); // DO NOT DELETE! - if(Selectors.hasOwnProperty(selector_name)){ - var milliseconds = Selectors[selector_name].duration; - elements = elements.transition().duration(milliseconds); - } - if(g_info.aes.hasOwnProperty("id")){ - elements.attr("id", id_fun); - } - if(g_info.aes.hasOwnProperty("href")){ - // elements are , children are e.g. - var linked_geoms = elements.select(eAppend); - // d3.select(linked_geoms).data(data, key_fun); // WHY did we need this? - eActions(linked_geoms); - linkActions(elements); - }else{ - // elements are e.g. - eActions(elements); // Set the attributes of all elements (enter/exit/stay) - } - }; // Refactor geom based on OOP, more specific geom can be extended from // the basic Geom class From a9d97f98f4f29fdb490c5e14ecc1c7e0c3141c88 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Thu, 12 Oct 2023 22:17:24 -0700 Subject: [PATCH 08/22] remove if-else for group geom --- inst/htmljs/animint.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 63949f27b..f80b3efee 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -895,7 +895,7 @@ var animint = function (to_select, json_file) { var p_name = g_names[g_names.length - 1]; var panels = Plots[p_name].layout.PANEL; panels.forEach(function(panel) { - draw_geom(g_info, chunk, selector_name, panel); + var geomInstance = new Geom(g_info, chunk, selector_name, panel, SVGs, Plots, Selectors); }); }; @@ -1003,16 +1003,6 @@ var animint = function (to_select, json_file) { this.init_styles(g_info); this.init_key_id(g_info); this.init_select_style(); - - if (this.g_info.data_is_object) { - this.prepare_special_data(); - this.define_line_type(); - this.setup_data_binding(); - this.define_element_actions(); - this.define_link_actions(); - } else { - this.define_simple_link_actions(); - } } // Method to handle subset_order and selected_arrays From ab17af9e2f6613157c85d6847615226626062ca2 Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:56:03 -0700 Subject: [PATCH 09/22] Create GroupGeom/UngroupGeom virtual classes and convert `prepare_data()` into subclass method --- inst/htmljs/animint.js | 118 +++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 35 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index f80b3efee..e809d1e7c 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -997,7 +997,6 @@ var animint = function (to_select, json_file) { this.selected_arrays = [[]]; this.handle_subset_order(); - this.data = this.prepare_data(); this.Selectors = Selectors; this.init_styles(g_info); @@ -1047,40 +1046,41 @@ var animint = function (to_select, json_file) { } // Method to prepare data for data-binding - prepare_data() { - let data; - if (this.g_info.data_is_object) { - data = {}; - } else { - data = []; - } - this.selected_arrays.forEach((value_array) => { - let some_data = this.chunk; - value_array.forEach((value) => { - if (some_data.hasOwnProperty(value)) { - some_data = some_data[value]; - } else { - if (this.g_info.data_is_object) { - some_data = {}; - } else { - some_data = []; - } - } - }); - if (this.g_info.data_is_object) { - if (Array.isArray(some_data) && some_data.length) { - data['0'] = some_data; - } else { - for (let k in some_data) { - data[k] = some_data[k]; - } - } - } else { - data = data.concat(some_data); - } - }); - return data; - } + // TODO: this method should be in GroupGeom/UngroupGeom subclass + //prepare_data() { + // let data; + // if (this.g_info.data_is_object) { + // data = {}; + // } else { + // data = []; + // } + // this.selected_arrays.forEach((value_array) => { + // let some_data = this.chunk; + // value_array.forEach((value) => { + // if (some_data.hasOwnProperty(value)) { + // some_data = some_data[value]; + // } else { + // if (this.g_info.data_is_object) { + // some_data = {}; + // } else { + // some_data = []; + // } + // } + // }); + // if (this.g_info.data_is_object) { + // if (Array.isArray(some_data) && some_data.length) { + // data['0'] = some_data; + // } else { + // for (let k in some_data) { + // data[k] = some_data[k]; + // } + // } + // } else { + // data = data.concat(some_data); + // } + // }); + // return data; + //} // Initialize styles from g_info params init_styles(g_info) { @@ -1294,6 +1294,7 @@ var animint = function (to_select, json_file) { } // update each individual graphical element('e') + // TODO: move styleSetter group geom into specific subclass define_element_actions() { this.eActions = (e) => { const getGroupAndRow = (group_info) => { @@ -1373,6 +1374,53 @@ var animint = function (to_select, json_file) { } } + class GroupGeom extends Geom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); + this.data = this.prepare_data(); + } + + // this.g_info.data_is_object == true, line/path/ribbon/polygon + prepare_data() { + let data = {}; + + this.selected_arrays.forEach((value_array) => { + let some_data = this.chunk; + for (const value of value_array) { + some_data = some_data.hasOwnProperty(value) ? some_data[value] : {}; + } + + if (Array.isArray(some_data) && some_data.length) { + data['0'] = some_data; // If some_data is an array and not empty, assign to key '0' + } else { + data = { ...data, ...some_data }; // merge keys and values into the data object + } + + }); + return data; + } + } + + class UngroupGeom extends Geom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); + this.data = this.prepare_data(); + } + + // this.g_info.data_is_object == false + prepare_data() { + let data = []; + this.selected_arrays.forEach((value_array) => { + let some_data = this.chunk; + for (const value of value_array) { + some_data = some_data.hasOwnProperty(value) ? some_data[value] : []; + } + data = data.concat(some_data); + }); + return data; + } + } + // update scales for the plots that have update_axes option in // theme_animint function update_scales(p_name, axes, v_name, value){ From 6e518323a49bf9e7c33c8d1ba1a4d57276739908 Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Fri, 20 Oct 2023 17:25:47 -0700 Subject: [PATCH 10/22] optimize some code and add some todos --- inst/htmljs/animint.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index e809d1e7c..63598f026 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -985,9 +985,12 @@ var animint = function (to_select, json_file) { const p_name = g_names[g_names.length - 1]; this.scales = Plots[p_name].scales[this.PANEL]; - this.layer_g_element = this.svg.select('g.' + g_info.classed); - this.panel_g_element = this.layer_g_element.select('g.PANEL' + PANEL); - this.elements = this.panel_g_element.selectAll('.geom'); + this.elements = this.svg.select('g.' + this.g_info.classed) + .select('g.PANEL' + this.PANEL) + .selectAll('.geom'); + + // aesthetic + this.aes = this.g_info.aes; // selection features this.has_clickSelects = this.g_info.aes.hasOwnProperty('clickSelects'); @@ -1090,7 +1093,8 @@ var animint = function (to_select, json_file) { this.off_opacity = g_info.params.hasOwnProperty('alpha_off') ? g_info.params.alpha_off : this.base_opacity - 0.5; - + + // TODO: the below should be moved to GeomText subclass this.size = g_info.geom === 'text' ? 12 : 2; this.size = g_info.params.hasOwnProperty('size') ? g_info.params.size @@ -1101,6 +1105,8 @@ var animint = function (to_select, json_file) { ? g_info.params.stroke : 1; // by default ggplot2 has 0.5, animint has 1 this.linetype = g_info.params.linetype || 'solid'; + + // TODO: the below should be moved to GeomRect subclass this.colour = g_info.geom === 'rect' && has_clickSelects && From abaca42b795de5657b4743dad63f8a87fa494acd Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Fri, 20 Oct 2023 18:18:33 -0700 Subject: [PATCH 11/22] Add selecting group code in GroupGeom --- inst/htmljs/animint.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 63598f026..fc181ef26 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1384,6 +1384,15 @@ var animint = function (to_select, json_file) { constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); this.data = this.prepare_data(); + this.keyed_data = this.prepare_keyed_data(); + + this.kv = d3.entries(d3.keys(this.keyed_data)).map(d => { + d.clickSelects = this.keyed_data[d.value][0].clickSelects; + return d; + }); + + // TODO: g_info.geom == 'ribbon' logic should be moved to GeomRibbon subclass + this.lineThing = d3.svg.line().x(toXY('x', 'x')).y(toXY('y', 'y')); } // this.g_info.data_is_object == true, line/path/ribbon/polygon @@ -1405,6 +1414,21 @@ var animint = function (to_select, json_file) { }); return data; } + + prepare_keyed_data() { + let keyed_data = {}; + + for (let group_id in this.data) { + const one_group = this.data[group_id]; + const one_row = one_group[0]; + let k = one_row.hasOwnProperty('key') ? one_row.key : group_id; + keyed_data[k] = one_group; + } + return keyed_data; + } + + // select the correct group before returning anything. + // TODO: move the `setup_data_binding()` in the superclass to here } class UngroupGeom extends Geom { From 5a6a152e27428986240ffc57ec67d362e7e555e8 Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:08:43 -0700 Subject: [PATCH 12/22] move the `setup_data_binding()` from Geom to GroupGeom --- inst/htmljs/animint.js | 180 +++++++++++++++++++++++++++-------------- 1 file changed, 121 insertions(+), 59 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index fc181ef26..4fe13de30 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -989,14 +989,15 @@ var animint = function (to_select, json_file) { .select('g.PANEL' + this.PANEL) .selectAll('.geom'); - // aesthetic - this.aes = this.g_info.aes; // selection features this.has_clickSelects = this.g_info.aes.hasOwnProperty('clickSelects'); this.has_clickSelects_variable = this.g_info.aes.hasOwnProperty( 'clickSelects.variable' - ); + ); + + // aesthetic + this.aes = this.g_info.aes; this.selected_arrays = [[]]; this.handle_subset_order(); @@ -1049,41 +1050,7 @@ var animint = function (to_select, json_file) { } // Method to prepare data for data-binding - // TODO: this method should be in GroupGeom/UngroupGeom subclass - //prepare_data() { - // let data; - // if (this.g_info.data_is_object) { - // data = {}; - // } else { - // data = []; - // } - // this.selected_arrays.forEach((value_array) => { - // let some_data = this.chunk; - // value_array.forEach((value) => { - // if (some_data.hasOwnProperty(value)) { - // some_data = some_data[value]; - // } else { - // if (this.g_info.data_is_object) { - // some_data = {}; - // } else { - // some_data = []; - // } - // } - // }); - // if (this.g_info.data_is_object) { - // if (Array.isArray(some_data) && some_data.length) { - // data['0'] = some_data; - // } else { - // for (let k in some_data) { - // data[k] = some_data[k]; - // } - // } - // } else { - // data = data.concat(some_data); - // } - // }); - // return data; - //} + // TODO: this method should be in GroupGeom/UngroupGeom subclass (done) // Initialize styles from g_info params init_styles(g_info) { @@ -1093,8 +1060,8 @@ var animint = function (to_select, json_file) { this.off_opacity = g_info.params.hasOwnProperty('alpha_off') ? g_info.params.alpha_off : this.base_opacity - 0.5; - - // TODO: the below should be moved to GeomText subclass + + // TODO: the below line should be moved to GeomText subclass this.size = g_info.geom === 'text' ? 12 : 2; this.size = g_info.params.hasOwnProperty('size') ? g_info.params.size @@ -1104,8 +1071,8 @@ var animint = function (to_select, json_file) { this.stroke_width = g_info.params.hasOwnProperty('stroke') ? g_info.params.stroke : 1; // by default ggplot2 has 0.5, animint has 1 - this.linetype = g_info.params.linetype || 'solid'; - + this.linetype = g_info.params.linetype || 'solid'; + // TODO: the below should be moved to GeomRect subclass this.colour = g_info.geom === 'rect' && @@ -1284,21 +1251,6 @@ var animint = function (to_select, json_file) { } } - // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. - setup_data_binding() { - this.key_fun = function (group_info) { - return group_info.value; - }; - - this.id_fun = function (group_info) { - var one_group = this.keyed_data[group_info.value]; - var one_row = one_group[0]; - return one_row.id; - }.bind(this); - - this.elements = this.elements.data(this.kv, this.key_fun); - } - // update each individual graphical element('e') // TODO: move styleSetter group geom into specific subclass define_element_actions() { @@ -1391,8 +1343,9 @@ var animint = function (to_select, json_file) { return d; }); - // TODO: g_info.geom == 'ribbon' logic should be moved to GeomRibbon subclass + // TODO: g_info.geom == 'ribbon' logic should be moved to GeomRibbon subclass (done) this.lineThing = d3.svg.line().x(toXY('x', 'x')).y(toXY('y', 'y')); + } // this.g_info.data_is_object == true, line/path/ribbon/polygon @@ -1428,7 +1381,104 @@ var animint = function (to_select, json_file) { } // select the correct group before returning anything. - // TODO: move the `setup_data_binding()` in the superclass to here + // TODO: move the `setup_data_binding()` in the superclass to here (done) + // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. + setup_data_binding() { + this.key_fun = function (group_info) { + return group_info.value; + }; + + this.id_fun = function (group_info) { + var one_group = this.keyed_data[group_info.value]; + var one_row = one_group[0]; + return one_row.id; + }.bind(this); + + this.elements = this.elements.data(this.kv, this.key_fun); + this.linkActions = function (a_elements) { + a_elements + .attr('xlink:href', function (group_info) { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + return one_row.href; + }) + .attr('target', '_blank') + .attr('class', 'geom'); + }; + this.eActions = function (e) { + e.attr('d', function (d) { + var one_group = keyed_data[d.value]; + // filter NaN since they make the whole line disappear! + var no_na = one_group.filter(function (d) { + if (g_info.geom == 'ribbon') { + return !isNaN(d.x) && !isNaN(d.ymin) && !isNaN(d.ymax); + } else { + return !isNaN(d.x) && !isNaN(d.y); + } + }); + return this.lineThing(no_na); + }) + .style('fill', function (group_info) { + if (g_info.geom == 'line' || g_info.geom == 'path') { + return 'none'; + } + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + // take color for first value in the group + return this.get_fill(one_row); + }) + .style('stroke-width', function (group_info) { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + // take size for first value in the group + return this.get_size(one_row); + }) + .style('stroke', function (group_info) { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + // take color for first value in the group + // Since line/path geom are using group to draw, + // so it is different from other geom + // and cannot call select_style_fun function here + if ( + (has_clickSelects || has_clickSelects_variable) && + g_info.select_style.includes('stroke') + ) { + const v_name = + g_info.aes['clickSelects.variable'] || + g_info.aes['clickSelects']; + const s_info = Selectors[v_name]; + if (s_info.selected == one_row.clickSelects) { + return get_colour(one_row); + } else { + return get_colour_off(one_row); + } + } + return this.get_colour(one_row); + }) + .style('stroke-dasharray', function (group_info) { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + // take linetype for first value in the group + return this.get_dasharray(one_row); + }) + .style('stroke-width', function (group_info) { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + // take line size for first value in the group + return this.get_size(one_row); + }); + if (!g_info.select_style.includes('opacity')) { + e.style('opacity', function (group_info) { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + // take line size for first value in the group + return this.get_alpha(one_row); + }); + } + }; + this.eAppend = 'path'; + } } class UngroupGeom extends Geom { @@ -1451,6 +1501,18 @@ var animint = function (to_select, json_file) { } } + class GeomRibbon extends GroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + + this.lineThing = d3.svg + .area() + .x(toXY('x', 'x')) + .y(toXY('y', 'ymax')) + .y0(toXY('y', 'ymin')); + } + } + // update scales for the plots that have update_axes option in // theme_animint function update_scales(p_name, axes, v_name, value){ From 18bd925e12bba0cc41fc13a42341a211ae607f01 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 16:06:13 -0700 Subject: [PATCH 13/22] Refactor methods to use class properties; align GroupGeom method names with base class --- inst/htmljs/animint.js | 89 ++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 4fe13de30..5328726d3 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -985,26 +985,26 @@ var animint = function (to_select, json_file) { const p_name = g_names[g_names.length - 1]; this.scales = Plots[p_name].scales[this.PANEL]; - this.elements = this.svg.select('g.' + this.g_info.classed) - .select('g.PANEL' + this.PANEL) - .selectAll('.geom'); - + this.elements = this.svg + .select('g.' + this.g_info.classed) + .select('g.PANEL' + this.PANEL) + .selectAll('.geom'); // selection features this.has_clickSelects = this.g_info.aes.hasOwnProperty('clickSelects'); this.has_clickSelects_variable = this.g_info.aes.hasOwnProperty( 'clickSelects.variable' - ); + ); // aesthetic - this.aes = this.g_info.aes; + this.aes = this.g_info.aes; this.selected_arrays = [[]]; this.handle_subset_order(); this.Selectors = Selectors; - this.init_styles(g_info); - this.init_key_id(g_info); + this.init_styles(); + this.init_key_id(); this.init_select_style(); } @@ -1053,41 +1053,41 @@ var animint = function (to_select, json_file) { // TODO: this method should be in GroupGeom/UngroupGeom subclass (done) // Initialize styles from g_info params - init_styles(g_info) { - this.base_opacity = g_info.params.hasOwnProperty('alpha') - ? g_info.params.alpha + init_styles() { + this.base_opacity = this.g_info.params.hasOwnProperty('alpha') + ? this.g_info.params.alpha : 1; - this.off_opacity = g_info.params.hasOwnProperty('alpha_off') - ? g_info.params.alpha_off + this.off_opacity = this.g_info.params.hasOwnProperty('alpha_off') + ? this.g_info.params.alpha_off : this.base_opacity - 0.5; - // TODO: the below line should be moved to GeomText subclass - this.size = g_info.geom === 'text' ? 12 : 2; - this.size = g_info.params.hasOwnProperty('size') - ? g_info.params.size + // TODO: the below line should be moved to GeomText subclass + this.size = this.g_info.geom === 'text' ? 12 : 2; + this.size = this.g_info.params.hasOwnProperty('size') + ? this.g_info.params.size : size; // stroke_width for geom_point - this.stroke_width = g_info.params.hasOwnProperty('stroke') - ? g_info.params.stroke + this.stroke_width = this.g_info.params.hasOwnProperty('stroke') + ? this.g_info.params.stroke : 1; // by default ggplot2 has 0.5, animint has 1 - this.linetype = g_info.params.linetype || 'solid'; + this.linetype = this.g_info.params.linetype || 'solid'; // TODO: the below should be moved to GeomRect subclass this.colour = - g_info.geom === 'rect' && + this.g_info.geom === 'rect' && has_clickSelects && - g_info.params.colour === 'transparent' + this.g_info.params.colour === 'transparent' ? 'black' - : g_info.params.colour || 'black'; - this.fill = g_info.params.fill - ? g_info.params.fill - : g_info.params.colour || this.colour; - this.angle = g_info.params.hasOwnProperty('angle') - ? g_info.params['angle'] + : this.g_info.params.colour || 'black'; + this.fill = this.g_info.params.fill + ? this.g_info.params.fill + : this.g_info.params.colour || this.colour; + this.angle = this.g_info.params.hasOwnProperty('angle') + ? this.g_info.params['angle'] : 0; - this.text_anchor = g_info.params.hasOwnProperty('anchor') - ? g_info.params['anchor'] + this.text_anchor = this.g_info.params.hasOwnProperty('anchor') + ? this.g_info.params['anchor'] : 'middle'; } @@ -1183,12 +1183,12 @@ var animint = function (to_select, json_file) { } // initialize key and id functions based on g_info - init_key_id(g_info) { + init_key_id() { this.id_fun = (d) => { return d.id; }; - if (g_info.aes.hasOwnProperty('key')) { + if (this.g_info.aes.hasOwnProperty('key')) { this.key_fun = (d) => { return d.key; }; @@ -1275,7 +1275,10 @@ var animint = function (to_select, json_file) { const styleSetter = (group_info) => { const { one_row } = getGroupAndRow(group_info); return { - fill: this.g_info.geom === 'line' || this.g_info.geom === 'path' ? 'none' : this.get_fill(one_row), + fill: + this.g_info.geom === 'line' || this.g_info.geom === 'path' + ? 'none' + : this.get_fill(one_row), strokeWidth: this.get_size(one_row), stroke: this.get_colour(one_row), strokeDasharray: this.get_dasharray(one_row), @@ -1383,16 +1386,18 @@ var animint = function (to_select, json_file) { // select the correct group before returning anything. // TODO: move the `setup_data_binding()` in the superclass to here (done) // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. - setup_data_binding() { - this.key_fun = function (group_info) { - return group_info.value; - }; + // TODO: should this method renamed to `init_key_id`? the Geom base class has the method + // would it be inherited from base Geom class? + init_key_id() { + this.key_fun = (group_info) => { + return group_info.value; + } - this.id_fun = function (group_info) { - var one_group = this.keyed_data[group_info.value]; - var one_row = one_group[0]; - return one_row.id; - }.bind(this); + this.id_fun = (group_info) => { + var one_group = keyed_data[group_info.value]; + var one_row = one_group[0]; + return one_row.id; + }; this.elements = this.elements.data(this.kv, this.key_fun); this.linkActions = function (a_elements) { From e70359567c2fe0a743864068791d01e4a99a2160 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 18:40:14 -0700 Subject: [PATCH 14/22] Create `setup_eActions()` in GroupGeom --- inst/htmljs/animint.js | 290 ++++++++++++++++++++++------------------- 1 file changed, 154 insertions(+), 136 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 5328726d3..af9e795c8 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1336,154 +1336,153 @@ var animint = function (to_select, json_file) { } class GroupGeom extends Geom { - constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { - super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); - this.data = this.prepare_data(); - this.keyed_data = this.prepare_keyed_data(); - - this.kv = d3.entries(d3.keys(this.keyed_data)).map(d => { - d.clickSelects = this.keyed_data[d.value][0].clickSelects; - return d; - }); + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); + this.data = this.prepare_data(); + this.keyed_data = this.prepare_keyed_data(); + + this.kv = d3.entries(d3.keys(this.keyed_data)).map((d) => { + d.clickSelects = this.keyed_data[d.value][0].clickSelects; + return d; + }); - // TODO: g_info.geom == 'ribbon' logic should be moved to GeomRibbon subclass (done) - this.lineThing = d3.svg.line().x(toXY('x', 'x')).y(toXY('y', 'y')); + // TODO: g_info.geom == 'ribbon' logic should be moved to GeomRibbon subclass (done) + this.lineThing = d3.svg.line().x(toXY('x', 'x')).y(toXY('y', 'y')); + } - } + // this.g_info.data_is_object == true, line/path/ribbon/polygon + prepare_data() { + let data = {}; - // this.g_info.data_is_object == true, line/path/ribbon/polygon - prepare_data() { - let data = {}; + this.selected_arrays.forEach((value_array) => { + let some_data = this.chunk; + for (const value of value_array) { + some_data = some_data.hasOwnProperty(value) ? some_data[value] : {}; + } - this.selected_arrays.forEach((value_array) => { - let some_data = this.chunk; - for (const value of value_array) { - some_data = some_data.hasOwnProperty(value) ? some_data[value] : {}; - } + if (Array.isArray(some_data) && some_data.length) { + data['0'] = some_data; // If some_data is an array and not empty, assign to key '0' + } else { + data = { ...data, ...some_data }; // merge keys and values into the data object + } + }); + return data; + } - if (Array.isArray(some_data) && some_data.length) { - data['0'] = some_data; // If some_data is an array and not empty, assign to key '0' - } else { - data = { ...data, ...some_data }; // merge keys and values into the data object - } + prepare_keyed_data() { + let keyed_data = {}; - }); - return data; + for (let group_id in this.data) { + const one_group = this.data[group_id]; + const one_row = one_group[0]; + let k = one_row.hasOwnProperty('key') ? one_row.key : group_id; + keyed_data[k] = one_group; } + return keyed_data; + } - prepare_keyed_data() { - let keyed_data = {}; - - for (let group_id in this.data) { - const one_group = this.data[group_id]; - const one_row = one_group[0]; - let k = one_row.hasOwnProperty('key') ? one_row.key : group_id; - keyed_data[k] = one_group; - } - return keyed_data; - } + // select the correct group before returning anything. + // TODO: move the `setup_data_binding()` in the superclass to here (done) + // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. + // TODO: should this method renamed to `init_key_id`? the Geom base class has the method + // would it be inherited from base Geom class? + init_key_id() { + this.key_fun = (group_info) => { + return group_info.value; + }; - // select the correct group before returning anything. - // TODO: move the `setup_data_binding()` in the superclass to here (done) - // set up the D3 data binding by defining the key_fun and id_fun, and then binding the data kv to the elements. - // TODO: should this method renamed to `init_key_id`? the Geom base class has the method - // would it be inherited from base Geom class? - init_key_id() { - this.key_fun = (group_info) => { - return group_info.value; - } + this.id_fun = (group_info) => { + var one_group = this.keyed_data[group_info.value]; + var one_row = one_group[0]; + return one_row.id; + }; + } - this.id_fun = (group_info) => { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - return one_row.id; - }; + setup_linkActions() { + this.elements = this.elements.data(this.kv, this.key_fun); + this.linkActions = (a_elements) => { + const hrefs = a_elements.data().map(group_info => { + const one_group = this.keyed_data[group_info.value]; + return one_group[0].href; + }); + a_elements + .attr('xlink:href', (d, i) => hrefs[i]) + .attr('target', '_blank') + .attr('class', 'geom'); + }; + } - this.elements = this.elements.data(this.kv, this.key_fun); - this.linkActions = function (a_elements) { - a_elements - .attr('xlink:href', function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - return one_row.href; - }) - .attr('target', '_blank') - .attr('class', 'geom'); - }; - this.eActions = function (e) { - e.attr('d', function (d) { - var one_group = keyed_data[d.value]; - // filter NaN since they make the whole line disappear! - var no_na = one_group.filter(function (d) { - if (g_info.geom == 'ribbon') { - return !isNaN(d.x) && !isNaN(d.ymin) && !isNaN(d.ymax); - } else { - return !isNaN(d.x) && !isNaN(d.y); - } - }); - return this.lineThing(no_na); - }) - .style('fill', function (group_info) { - if (g_info.geom == 'line' || g_info.geom == 'path') { - return 'none'; - } - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take color for first value in the group - return this.get_fill(one_row); - }) - .style('stroke-width', function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take size for first value in the group - return this.get_size(one_row); - }) - .style('stroke', function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take color for first value in the group - // Since line/path geom are using group to draw, - // so it is different from other geom - // and cannot call select_style_fun function here - if ( - (has_clickSelects || has_clickSelects_variable) && - g_info.select_style.includes('stroke') - ) { - const v_name = - g_info.aes['clickSelects.variable'] || - g_info.aes['clickSelects']; - const s_info = Selectors[v_name]; - if (s_info.selected == one_row.clickSelects) { - return get_colour(one_row); - } else { - return get_colour_off(one_row); - } - } - return this.get_colour(one_row); - }) - .style('stroke-dasharray', function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take linetype for first value in the group - return this.get_dasharray(one_row); - }) - .style('stroke-width', function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take line size for first value in the group - return this.get_size(one_row); - }); - if (!g_info.select_style.includes('opacity')) { - e.style('opacity', function (group_info) { - var one_group = keyed_data[group_info.value]; - var one_row = one_group[0]; - // take line size for first value in the group - return this.get_alpha(one_row); - }); + setup_eActions() { + this.eActions = (e) => { + const get_data_for_row = (d) => { + const group = this.keyed_data[d.value]; + return group && group[0]; + } + e.attr('d', (d) => { + const one_row = get_data_for_row(d); + if (!one_row) return; + // filter NaN since they make the whole line disappear! + const valid_data = one_row.filter((d) => { + return !isNaN(d.x) && !isNaN(d.y); + }) + return this.lineThing(valid_data); + }) + .style('fill', (group_info) => { + // TODO: move to GeomLine and GeomPath + // if (g_info.geom == 'line' || g_info.geom == 'path') { + // return 'none'; + // } + const one_row = get_data_for_row(group_info); + // take color for first value in the group + return this.get_fill(one_row); + }) + .style('stroke-width', (group_info) => { + const one_row = get_data_for_row(group_info); + // take size for first value in the group + return this.get_size(one_row); + }) + .style('stroke', (group_info) => { + const one_row = get_data_for_row(group_info); + // take color for first value in the group + // Since line/path geom are using group to draw, + // so it is different from other geom + // and cannot call select_style_fun function here + if ( + // has_clickSelects may not be found inside the class + // need to set the variables into global? + (has_clickSelects || has_clickSelects_variable) && this.g_info.select_style.includes('stroke') + ) { + const v_name = + this.g_info.aes['clickSelects.variable'] || this.g_info.aes['clickSelects']; + const s_info = Selectors[v_name]; + if (s_info.selected == one_row.clickSelects) { + return this.get_colour(one_row); + } else { + return this.get_colour_off(one_row); } - }; - this.eAppend = 'path'; - } + } + return this.get_colour(one_row); + }) + .style('stroke-dasharray', (group_info) => { + const one_row = get_data_for_row(group_info); + // take linetype for first value in the group + return this.get_dasharray(one_row); + }) + .style('stroke-width', (group_info) => { + const one_row = get_data_for_row(group_info); + // take line size for first value in the group + return this.get_size(one_row); + }); + if (!this.g_info.select_style.includes('opacity')) { + e.style('opacity', (group_info) => { + const one_row = get_data_for_row(group_info); + // take line size for first value in the group + return this.get_alpha(one_row); + }); + } + }; + this.eAppend = 'path'; + } } class UngroupGeom extends Geom { @@ -1516,6 +1515,25 @@ var animint = function (to_select, json_file) { .y(toXY('y', 'ymax')) .y0(toXY('y', 'ymin')); } + + setup_eActions() { + this.eActions = (e) => { + const get_data_for_row = (d) => { + const group = this.keyed_data[d.value]; + return group && group[0]; + } + e.attr('d', function (d) { + const one_row = get_data_for_row(d); + if (!one_row) return; + // filter NaN since they make the whole line disappear! + const valid_data = one_row.filter((d) => { + return !isNaN(d.x) && !isNaN(d.ymin) && !isNaN(d.ymax); + }) + return this.lineThing(valid_data); + }) + }; + this.eAppend = 'path'; + } } // update scales for the plots that have update_axes option in From 08b5907335b122c2529b10ce68192b13db5694d0 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 21:30:40 -0700 Subject: [PATCH 15/22] create GeomSegment class --- inst/htmljs/animint.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index af9e795c8..3d7b2e63c 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1503,6 +1503,17 @@ var animint = function (to_select, json_file) { }); return data; } + + setup_linkActions() { + this.linkActions = (a_elements) => { + a_elements + .attr('xlink:href', (d) => { + return d.href; + }) + .attr('target', '_blank') + .attr('class', 'geom'); + }; + } } class GeomRibbon extends GroupGeom { @@ -1536,6 +1547,33 @@ var animint = function (to_select, json_file) { } } + class GeomSegment extends UngroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + } + setup_eActions() { + this.elements = this.elements.data(this.data, this.key_fun); + this.eActions = (e) => { + e.attr('x1', (d) => { + return this.scales.x(d['x']); + }) + .attr('x2', (d) => { + return this.scales.x(d['xend']); + }) + .attr('y1', (d) => { + return this.scales.y(d['y']); + }) + .attr('y2', (d) => { + return this.scales.y(d['yend']); + }) + .style('stroke-dasharray', this.get_dasharray) + .style('stroke-width', this.get_size); + this.select_style_fun(this.g_info, e); + }; + this.eAppend = 'line'; + } + } + // update scales for the plots that have update_axes option in // theme_animint function update_scales(p_name, axes, v_name, value){ From 2c29444d4ebc4cfdb0c94956ec7f4c2990f04403 Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 21:41:41 -0700 Subject: [PATCH 16/22] Create GeomLinerange, GeomVline class --- inst/htmljs/animint.js | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 3d7b2e63c..9d071b6c1 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1574,6 +1574,52 @@ var animint = function (to_select, json_file) { } } + class GeomLinerange extends UngroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + } + setup_eActions() { + this.elements = this.elements.data(this.data, this.key_fun); + this.eActions = (e) => { + e.attr('x1', (d) => { + return this.scales.x(d['x']); + }) + .attr('x2', (d) => { + return this.scales.x(d['x']); + }) + .attr('y1', (d) => { + return this.scales.y(d['ymax']); + }) + .attr('y2', (d) => { + return this.scales.y(d['ymin']); + }) + .style('stroke-dasharray', this.get_dasharray) + .style('stroke-width', this.get_size); + this.select_style_fun(this.g_info, e); + }; + this.eAppend = 'line'; + } + } + + class GeomVline extends UngroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + } + setup_eActions() { + this.elements = this.elements.data(this.data, this.key_fun); + this.eActions = (e) => { + e.attr('x1', this.toXY('x', 'xintercept')) + .attr('x2', this.toXY('x', 'xintercept')) + .attr('y1', this.scales.y.range()[0]) + .attr('y2', this.scales.y.range()[1]) + .style('stroke-dasharray', this.get_dasharray) + .style('stroke-width', this.get_size); + this.select_style_fun(this.g_info, e); + }; + this.eAppend = 'line'; + } + } + // update scales for the plots that have update_axes option in // theme_animint function update_scales(p_name, axes, v_name, value){ From c207aecfe0dbe914563ea2a5b08b9bc4e08fa93e Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 21:49:48 -0700 Subject: [PATCH 17/22] Create GeomHline class --- inst/htmljs/animint.js | 93 +++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 9d071b6c1..3e47a9201 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1486,34 +1486,40 @@ var animint = function (to_select, json_file) { } class UngroupGeom extends Geom { - constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { - super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); - this.data = this.prepare_data(); - } + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, Selector_name, PANEL, SVGs, Plots, Selectors); + this.data = this.prepare_data(); + } - // this.g_info.data_is_object == false - prepare_data() { - let data = []; - this.selected_arrays.forEach((value_array) => { - let some_data = this.chunk; - for (const value of value_array) { - some_data = some_data.hasOwnProperty(value) ? some_data[value] : []; - } - data = data.concat(some_data); - }); - return data; - } + // this.g_info.data_is_object == false + prepare_data() { + let data = []; + this.selected_arrays.forEach((value_array) => { + let some_data = this.chunk; + for (const value of value_array) { + some_data = some_data.hasOwnProperty(value) ? some_data[value] : []; + } + data = data.concat(some_data); + }); + return data; + } - setup_linkActions() { - this.linkActions = (a_elements) => { - a_elements - .attr('xlink:href', (d) => { - return d.href; - }) - .attr('target', '_blank') - .attr('class', 'geom'); - }; - } + setup_linkActions() { + this.linkActions = (a_elements) => { + a_elements + .attr('xlink:href', (d) => { + return d.href; + }) + .attr('target', '_blank') + .attr('class', 'geom'); + }; + } + + apply_styles_selection(e) { + e.style('stroke-dasharray', this.get_dasharray) + .style('stroke-width',this.get_size); + this.select_style_fun(this.g_info, e); + } } class GeomRibbon extends GroupGeom { @@ -1565,10 +1571,8 @@ var animint = function (to_select, json_file) { }) .attr('y2', (d) => { return this.scales.y(d['yend']); - }) - .style('stroke-dasharray', this.get_dasharray) - .style('stroke-width', this.get_size); - this.select_style_fun(this.g_info, e); + }); + this.apply_styles_selection(e); }; this.eAppend = 'line'; } @@ -1592,10 +1596,8 @@ var animint = function (to_select, json_file) { }) .attr('y2', (d) => { return this.scales.y(d['ymin']); - }) - .style('stroke-dasharray', this.get_dasharray) - .style('stroke-width', this.get_size); - this.select_style_fun(this.g_info, e); + }); + this.apply_styles_selection(e); }; this.eAppend = 'line'; } @@ -1611,10 +1613,25 @@ var animint = function (to_select, json_file) { e.attr('x1', this.toXY('x', 'xintercept')) .attr('x2', this.toXY('x', 'xintercept')) .attr('y1', this.scales.y.range()[0]) - .attr('y2', this.scales.y.range()[1]) - .style('stroke-dasharray', this.get_dasharray) - .style('stroke-width', this.get_size); - this.select_style_fun(this.g_info, e); + .attr('y2', this.scales.y.range()[1]); + this.apply_styles_selection(e); + }; + this.eAppend = 'line'; + } + } + + class GeomHline extends UngroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + } + setup_eActions() { + this.elements = this.elements.data(this.data, this.key_fun); + this.eActions = (e) => { + e.attr('x1', this.toXY('y', 'yintercept')) + .attr('x2', this.toXY('y', 'yintercept')) + .attr('x1', this.scales.x.range()[0]) + .attr('x2', this.scales.x.range()[1]); + this.apply_styles_selection(e); }; this.eAppend = 'line'; } From 734655003e81a3cfaaed6975e20d6c0422a8110a Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 21:53:04 -0700 Subject: [PATCH 18/22] create GeomText class --- inst/htmljs/animint.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 3e47a9201..063d696f8 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1515,7 +1515,7 @@ var animint = function (to_select, json_file) { }; } - apply_styles_selection(e) { + apply_stroke_styles(e) { e.style('stroke-dasharray', this.get_dasharray) .style('stroke-width',this.get_size); this.select_style_fun(this.g_info, e); @@ -1572,7 +1572,7 @@ var animint = function (to_select, json_file) { .attr('y2', (d) => { return this.scales.y(d['yend']); }); - this.apply_styles_selection(e); + this.apply_stroke_styles(e); }; this.eAppend = 'line'; } @@ -1597,7 +1597,7 @@ var animint = function (to_select, json_file) { .attr('y2', (d) => { return this.scales.y(d['ymin']); }); - this.apply_styles_selection(e); + this.apply_stroke_styles(e); }; this.eAppend = 'line'; } @@ -1614,7 +1614,7 @@ var animint = function (to_select, json_file) { .attr('x2', this.toXY('x', 'xintercept')) .attr('y1', this.scales.y.range()[0]) .attr('y2', this.scales.y.range()[1]); - this.apply_styles_selection(e); + this.apply_stroke_styles(e); }; this.eAppend = 'line'; } @@ -1631,12 +1631,32 @@ var animint = function (to_select, json_file) { .attr('x2', this.toXY('y', 'yintercept')) .attr('x1', this.scales.x.range()[0]) .attr('x2', this.scales.x.range()[1]); - this.apply_styles_selection(e); + this.apply_stroke_styles(e); }; this.eAppend = 'line'; } } + class GeomText extends UngroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + } + setup_eActions() { + this.elements = this.elements.data(this.data, this.key_fun); + this.eActions = (e) => { + e.attr('x', this.toXY('x', 'x')) + .attr('y', this.toXY('y', 'y')) + .attr('font-size', this.get_size) + .style('text-anchor', this.get_text_anchor) + .attr('transform', this.get_angle) + .text((d) => { + return d.label; + }); + }; + eAppend = 'text'; + } + } + // update scales for the plots that have update_axes option in // theme_animint function update_scales(p_name, axes, v_name, value){ From 4ea7bc0efd9ffa316d89385445b11e23e1ad5aaf Mon Sep 17 00:00:00 2001 From: Faye-yufan Date: Sat, 4 Nov 2023 21:59:21 -0700 Subject: [PATCH 19/22] create GeomPoint class --- inst/htmljs/animint.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 063d696f8..9d1891fbc 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1199,14 +1199,14 @@ var animint = function (to_select, json_file) { // Initialize the selection style function init_select_style() { - this.select_style_fun = (g_info, e) => { - if (!g_info.select_style.includes('stroke')) { + this.select_style_fun = (e) => { + if (!this.g_info.select_style.includes('stroke')) { e.style('stroke', this.get_colour); } - if (!g_info.select_style.includes('opacity')) { + if (!this.g_info.select_style.includes('opacity')) { e.style('opacity', this.get_alpha); } - if (!g_info.select_style.includes('fill')) { + if (!this.g_info.select_style.includes('fill')) { e.style('fill', this.get_fill); } }; @@ -1518,7 +1518,6 @@ var animint = function (to_select, json_file) { apply_stroke_styles(e) { e.style('stroke-dasharray', this.get_dasharray) .style('stroke-width',this.get_size); - this.select_style_fun(this.g_info, e); } } @@ -1573,6 +1572,7 @@ var animint = function (to_select, json_file) { return this.scales.y(d['yend']); }); this.apply_stroke_styles(e); + this.select_style_fun(e); }; this.eAppend = 'line'; } @@ -1598,6 +1598,7 @@ var animint = function (to_select, json_file) { return this.scales.y(d['ymin']); }); this.apply_stroke_styles(e); + this.select_style_fun(e); }; this.eAppend = 'line'; } @@ -1615,6 +1616,7 @@ var animint = function (to_select, json_file) { .attr('y1', this.scales.y.range()[0]) .attr('y2', this.scales.y.range()[1]); this.apply_stroke_styles(e); + this.select_style_fun(e); }; this.eAppend = 'line'; } @@ -1632,6 +1634,7 @@ var animint = function (to_select, json_file) { .attr('x1', this.scales.x.range()[0]) .attr('x2', this.scales.x.range()[1]); this.apply_stroke_styles(e); + this.select_style_fun(e); }; this.eAppend = 'line'; } @@ -1657,6 +1660,23 @@ var animint = function (to_select, json_file) { } } + class GeomPoint extends UngroupGeom { + constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { + super(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors); + } + setup_eActions() { + this.elements = this.elements.data(this.data, this.key_fun); + this.eActions = (e) => { + e.attr('cx', toXY('x', 'x')) + .attr('cy', toXY('y', 'y')) + .attr('r', this.get_size) + .style('stroke-width', this.get_stroke_width); + this.select_style_fun(this.g_info, e); + }; + this.eAppend = 'circle'; + } + } + // update scales for the plots that have update_axes option in // theme_animint function update_scales(p_name, axes, v_name, value){ From ee7615c75d38712e7485d543678e2378f2b0337d Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:29:41 -0800 Subject: [PATCH 20/22] merge master --- inst/htmljs/animint.js | 202 ++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 106 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index e0bf1c459..b9bc250b2 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -862,7 +862,7 @@ var animint = function (to_select, json_file) { var p_name = g_names[g_names.length - 1]; var panels = Plots[p_name].layout.PANEL; panels.forEach(function(panel) { - var geomInstance = new Geom(g_info, chunk, selector_name, panel, SVGs, Plots, Selectors); + draw_geom(g_info, chunk, selector_name, panel); }); }; @@ -934,87 +934,82 @@ var animint = function (to_select, json_file) { }); }//download_chunk. - // Refactor geom based on OOP, more specific geom can be extended from - // the basic Geom class - class Geom { - constructor(g_info, chunk, selector_name, PANEL, SVGs, Plots, Selectors) { - this.g_info = g_info; - this.chunk = chunk; - this.selector_name = selector_name; - this.PANEL = PANEL; - this.scales = {}; - - this.g_info.tr.select('td.status').text('displayed'); - - // SVG and scales - this.svg = SVGs[this.g_info.classsed]; - const g_names = this.g_info.classed.split('_'); - const p_name = g_names[g_names.length - 1]; - this.scales = Plots[p_name].scales[this.PANEL]; - - this.elements = this.svg - .select('g.' + this.g_info.classed) - .select('g.PANEL' + this.PANEL) - .selectAll('.geom'); - - // selection features - this.has_clickSelects = this.g_info.aes.hasOwnProperty('clickSelects'); - this.has_clickSelects_variable = this.g_info.aes.hasOwnProperty( - 'clickSelects.variable' - ); - - // aesthetic - this.aes = this.g_info.aes; - - this.selected_arrays = [[]]; - this.handle_subset_order(); - this.Selectors = Selectors; - - this.init_styles(); - this.init_key_id(); - this.init_select_style(); - } - - // Method to handle subset_order and selected_arrays - handle_subset_order() { - this.g_info.subset_order.forEach((aes_name) => { - let selected, values; - let new_arrays = []; - if (0 < aes_name.indexOf('.variable')) { - this.selected_arrays.forEach((old_array) => { - let some_data = this.chunk; - old_array.forEach((value) => { - if (some_data.hasOwnProperty(value)) { - some_data = some_data[value]; - } else { - some_data = {}; - } - }); - values = d3.keys(some_data); - values.forEach((s_name) => { - selected = this.Selectors[s_name].selected; - let new_array = old_array.concat(s_name).concat(selected); - new_arrays.push(new_array); - }); - }); + // update_geom is responsible for obtaining a chunk of downloaded + // data, and then calling draw_geom to actually draw it. + var draw_geom = function(g_info, chunk, selector_name, PANEL){ + g_info.tr.select("td.status").text("displayed"); + var svg = SVGs[g_info.classed]; + // derive the plot name from the geometry name + var g_names = g_info.classed.split("_"); + var p_name = g_names[g_names.length - 1]; + var scales = Plots[p_name].scales[PANEL]; + var selected_arrays = [ [] ]; //double array necessary. + var has_clickSelects = g_info.aes.hasOwnProperty("clickSelects"); + var has_clickSelects_variable = + g_info.aes.hasOwnProperty("clickSelects.variable"); + g_info.subset_order.forEach(function (aes_name) { + var selected, values; + var new_arrays = []; + if(0 < aes_name.indexOf(".variable")){ + selected_arrays.forEach(function(old_array){ + var some_data = chunk; + old_array.forEach(function(value){ + if(some_data.hasOwnProperty(value)) { + some_data = some_data[value]; + } else { + some_data = {}; + } + }) + values = d3.keys(some_data); + values.forEach(function(s_name){ + var selected = Selectors[s_name].selected; + var new_array = old_array.concat(s_name).concat(selected); + new_arrays.push(new_array); + }) + }) + }else{//not .variable aes: + if(aes_name == "PANEL"){ + selected = PANEL; + }else{ + var s_name = g_info.aes[aes_name]; + selected = Selectors[s_name].selected; + } + if(isArray(selected)){ + values = selected; //multiple selection. + }else{ + values = [selected]; //single selection. + } + values.forEach(function(value){ + selected_arrays.forEach(function(old_array){ + var new_array = old_array.concat(value); + new_arrays.push(new_array); + }) + }) + } + selected_arrays = new_arrays; + }); + // data can be either an array[] if it will be directly involved + // in a data-bind, or an object{} if it will be involved in a + // data-bind by group (e.g. geom_line). + var data; + if(g_info.data_is_object){ + data = {}; + }else{ + data = []; + } + selected_arrays.forEach(function(value_array){ + var some_data = chunk; + value_array.forEach(function(value){ + if (some_data.hasOwnProperty(value)) { + some_data = some_data[value]; } else { - if (aes_name === 'PANEL') { - selected = this.PANEL; - } else { - const s_name = this.g_info.aes[aes_name]; - selected = this.Selectors[s_name].selected; - } - values = Array.isArray(selected) ? selected : [selected]; - values.forEach((value) => { - this.selected_arrays.forEach((old_array) => { - let new_array = old_array.concat(value); - new_arrays.push(new_array); - }); - }); + if(g_info.data_is_object){ + some_data = {}; + }else{ + some_data = []; + } } - this.selected_arrays = new_arrays; }); - if(g_info.data_is_object){ if(isArray(some_data) && some_data.length){ data["0"] = some_data; @@ -1153,19 +1148,12 @@ var animint = function (to_select, json_file) { get_text_anchor = function(d){ return d["anchor"]; } - return null; // No default `fill_off` value - } - - get_text_anchor(d) { - if (this.text_anchor) { - return this.text_anchor; - } else if (d.hasOwnProperty('anchor')) { - return d['anchor']; + }else{ + get_text_anchor = function(d){ + return text_anchor; } - return 'middle'; } - var eActions, eAppend; var key_fun = null; if(g_info.aes.hasOwnProperty("key")){ @@ -1249,29 +1237,30 @@ var animint = function (to_select, json_file) { } keyed_data[k] = one_group; } + var kv_array = d3.entries(d3.keys(keyed_data)); + var kv = kv_array.map(function (d) { + //d[aes.group] = d.value; - let kv_array = d3.entries(d3.keys(keyed_data)); - this.kv = kv_array.map(function (d) { + // Need to store the clickSelects value that will + // be passed to the selector when we click on this + // item. d.clickSelects = keyed_data[d.value][0].clickSelects; return d; }); - } - // TODO: Move this to a geom_ribbon subclass method later. - define_line_type() { - if (this.g_info.geom === 'ribbon') { - this.lineThing = d3.svg - .area() - .x(this.toXY('x', 'x')) - .y(this.toXY('y', 'ymax')) - .y0(this.toXY('y', 'ymin')); + // line, path, and polygon use d3.svg.line(), + // ribbon uses d3.svg.area() + // we have to define lineThing accordingly. + if (g_info.geom == "ribbon") { + var lineThing = d3.svg.area() + .x(toXY("x", "x")) + .y(toXY("y", "ymax")) + .y0(toXY("y", "ymin")); } else { - this.lineThing = d3.svg - .line() - .x(this.toXY('x', 'x')) - .y(this.toXY('y', 'y')); + var lineThing = d3.svg.line() + .x(toXY("x", "x")) + .y(toXY("y", "y")); } - if(["line","path"].includes(g_info.geom)){ fill = "none"; fill_off = "none"; @@ -1293,8 +1282,9 @@ var animint = function (to_select, json_file) { var no_na = one_group.filter(function(d){ if(g_info.geom == "ribbon"){ return !isNaN(d.x) && !isNaN(d.ymin) && !isNaN(d.ymax); + }else{ + return !isNaN(d.x) && !isNaN(d.y); } - return !isNaN(d.x) && !isNaN(d.y); }); return lineThing(no_na); }) From 7d2eb647f7f143e61ff0344166e79a632e6246f0 Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:34:55 -0800 Subject: [PATCH 21/22] add get_values function --- inst/htmljs/animint.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index b9bc250b2..f9e37a12b 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1633,17 +1633,26 @@ var animint = function (to_select, json_file) { selector_url=selector_url.concat(sub_url); } } - setup_eActions() { - this.elements = this.elements.data(this.data, this.key_fun); - this.eActions = (e) => { - e.attr('x1', this.toXY('x', 'xintercept')) - .attr('x2', this.toXY('x', 'xintercept')) - .attr('y1', this.scales.y.range()[0]) - .attr('y2', this.scales.y.range()[1]); - this.apply_stroke_styles(e); - this.select_style_fun(e); - }; - this.eAppend = 'line'; + var url_nohash=window.location.href.match(/(^[^#]*)/)[0]; + selector_url=url_nohash.concat(selector_url); + return selector_url; + }; + + var get_values=function(){ + // function that is useful to get the selected values + var selected_values={} + for(var s_name in Selectors){ + var s_info=Selectors[s_name]; + var initial_selections = []; + if(s_info.type==="single"){ + initial_selections=[s_info.selected]; + } + else{ + for(var i in s_info.selected) { + initial_selections[i] = s_info.selected[i]; + } + } + selected_values[s_name]=initial_selections; } return selected_values; }; From 48107c56cbd0cf14e2b27b6f13b6a352e981c6b3 Mon Sep 17 00:00:00 2001 From: Yufan Fei <62975717+Faye-yufan@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:46:12 -0800 Subject: [PATCH 22/22] Remove code for handling .variable aes --- inst/htmljs/animint.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index f9e37a12b..7ae2dbb8a 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -951,22 +951,7 @@ var animint = function (to_select, json_file) { var selected, values; var new_arrays = []; if(0 < aes_name.indexOf(".variable")){ - selected_arrays.forEach(function(old_array){ - var some_data = chunk; - old_array.forEach(function(value){ - if(some_data.hasOwnProperty(value)) { - some_data = some_data[value]; - } else { - some_data = {}; - } - }) - values = d3.keys(some_data); - values.forEach(function(s_name){ - var selected = Selectors[s_name].selected; - var new_array = old_array.concat(s_name).concat(selected); - new_arrays.push(new_array); - }) - }) + // Some code for handling .variable aes, put comment here as placeholder for testing }else{//not .variable aes: if(aes_name == "PANEL"){ selected = PANEL;