function curveBox(template_for_plotting){
let template_overall = template_for_plotting[0]["curve_box"]
let template_components = template_for_plotting[0]["components"]
let template_curves = template_components[0]["curves"][0]
let template_lines = template_components[0]["lines"]
let template_rectangles = template_components[0]["rectangles"]
let title = ""
if(template_overall["show_title"] != "yes"){let title = ""}
else{title=template_overall["title"]["text"]}
let width = template_overall["width"]
let height_multiplier_components = 0.95
if (template_overall["height_multiplier_components"]){
height_multiplier_components = template_overall["height_multiplier_components"]
}
let height = template_overall["height"]*height_multiplier_components
let height_components = template_overall["height"]
let margin = template_overall["margin"]
let header_sep_svg_or_not = template_overall["header_sep_svg_or_not"]
let svg_header_height = template_overall["svg_header_height"]
let gridlines = template_overall["gridlines"]
let gridlines_color = template_overall["gridlines_color"]
let gridlines_stroke_width = template_overall["gridlines_stroke_width"]
let mouseover_yes_or_no = template_overall["mouseover_yes_or_no"]
let mouseover_depth_or_depth_and_curve = template_overall["mouseover_depth_or_depth_and_curve"]
let mouseover_curvename = template_overall["mouseover_curvename"]
let mouseover_color_or_default_which_is_curve_color = template_overall["mouseover_color_or_default_which_is_curve_color"]
let secondary_depth_exist = "no"
//// Data is in d3.js form. An array of objects consisting of single level key:value pairs
let data = template_curves["data"]
//// Variables related to curves, these should all be arrays with one or more values!
let curve_names = template_curves["curve_names"]
let curve_colors = template_curves["curve_colors"]
let curve_stroke_dasharray = template_curves["curve_stroke_dasharray"]
let curve_name = curve_names[0]
let curve_color = curve_colors[0]
let curve_units = template_curves["curve_units"];
let scale_linear_log_or_yours = template_curves["scale_linear_log_or_yours"];
if(template_curves["curve_units"]){curve_units = template_curves["curve_units"]}
else{curve_units = ""}
//////// NEED TO MAKE THIS FLAG IN INPUT PLOTTING JSON
let flag_for_single_scale_or_independent_scales = template_overall["grouped_or_independent_x_scales"]
let grouped_or_independent_x_scale = template_overall["grouped_or_independent_x_scales"]
//// The depth_curve_name needs to be the same for all curves plotted!
let depth_curve_name = ""
if (template_curves["depth_curve_name"].length > 1 && typeof(template_curves["depth_curve_name"]) == "object" && template_curves["depth_curve_name"][0] !== template_curves["depth_curve_name"][1]
){
depth_curve_name = "depth_curve_name is not the same in two or more curves"
}
else{
depth_curve_name = template_curves["depth_curve_name"]
}
let depth_type_string = ""
if(
template_curves["depth_type_string"].length > 1 && typeof(template_curves["depth_type_string"]) == "object" && template_curves["depth_type_string"][0] != template_curves["depth_type_string"][1]
){
depth_type_string = "depth type string is not the same in two or more curves"
}
else if (template_curves["depth_type_string"][0] == ""){depth_type_string = ""}
else if (template_curves["depth_type_string"]){depth_type_string = "- "+template_curves["depth_type_string"]}
let depth_units_string = ""
if(template_curves["depth_units_string"] && template_curves["depth_units_string"][0] !== ""){
depth_units_string = "- " + template_curves["depth_units_string"]
}
///// THIS LINE BELOW DOESN"T MAKE ANY SENSE, CHANGE ////
let div_id = template_overall["div_id"]
if(template_overall["div_id"]){div_id = template_overall["div_id"]}
else{return "there_was_no_div_id_in_the_template"}
d3.select("#"+div_id).selectAll("*").remove();
///////// NEED TO FIX DEPTHS AS THERE ARE MULTIPLE DEPTH LIMITS AND THEY NEED TO BE CALCULATED PROPERLY !!!!! //////////////////////////
// //// Calculate depth min and max if depth min and/or max is not given explicitly in the template
let depth_min
let depth_max
if(!template_curves["depth_limits"] || template_curves["depth_limits"][0]["min"] == "autocalculate")
{depth_min = d3.min(data, function(d) { return +d[depth_curve_name];})}
else
{depth_min = template_curves["depth_limits"][0]["min"]}
//// max depth
if(!template_curves["depth_limits"] || template_curves["depth_limits"][0]["max"] == "autocalculate")
{depth_max = d3.max(data, function(d) { return +d[depth_curve_name];})}
else
{depth_max = template_curves["depth_limits"][0]["max"]}
// [depth_max,depth_min]
//// Apply depth min and max to incoming well log data
//// To save time, we'll first check if the first object in the array had as depth that is smaller than min
//// and check if the last object in the array has a depth that is larger than the max, if not. we do nothing.
if(data[0][depth_curve_name] > depth_min && data[-1][depth_curve_name] < depth_max){}
else{data = data.filter(function(objects){
return objects[depth_curve_name] > depth_min && objects[depth_curve_name] < depth_max;
})}
// let depth_min = -1000000
// let depth_max = 1000000
if(template_curves["min_depth"][0] == "autocalculate" || template_curves["min_depth"] == "autocalculate"){
depth_min = data[0][depth_curve_name]
}
else{depth_min = template_curves["min_depth"]}
if(template_curves["max_depth"][0] == "autocalculate" || template_curves["max_depth"] == "autocalculate"){
depth_max = data[data.length-1][depth_curve_name]
}
else{depth_max = template_curves["max_depth"]}
// let depth_min = template_curves["min_depth"][0]
// let depth_max = template_curves["max_depth"][0]
///////// <=== NEED TO FIX DEPTHS. THEY NEED TO BE CALCULATED PROPERLY !!!!! //////////////////////////
////////////// Initiate Divs + SVGs. Different depending single SVG or header separate =>//////////////
let svg = ""
let svg_holder = ""
let svg_header = ""
if (header_sep_svg_or_not == "yes"){
svg_holder = d3.select("#"+div_id).append("div")
.attr("class","svg_holder")
.style("overflow-x","auto")
svg_header = d3.select("#"+div_id+" div.svg_holder").append("svg")
svg_header.attr("class","header")
svg_header.attr("width",width)
.attr("height",svg_header_height); ///// THIS SHOULD BE CHANGED TO A KEY:VALUE PAIR IN TEMPLATES!!!
svg_header.append("g")
svg_header.style("display","block");
let depth_string_on_top = ""
if(depth_type_string == ""){
depth_string_on_top = depth_curve_name
}
else{depth_string_on_top = depth_type_string.replace("- ","")}
svg_header.append("text")
.attr("x", (margin.left)/2)
.attr("y", "1em")
.attr("text-anchor", "middle")
.style("font-size", "10px")
.style("text-decoration", "underline")
.text(depth_curve_name);
if(depth_type_string != ""){
let depth_type_string_x_pos = 0
let depth_type_string_x_pos_anch = "start"
if(secondary_depth_exist == "no"){
depth_type_string_x_pos = margin.left/2
depth_type_string_x_pos_anch = "middle"
}
svg_header.append("text")
.attr("x", depth_type_string_x_pos)
.attr("y", "3em")
.attr("text-anchor", depth_type_string_x_pos_anch)
.style("font-size", "8px")
.style("text-decoration", "underline")
.text(depth_string_on_top);
}
//svg_header.append("text")
// .attr("x", margin.left)
// .attr("y", "4em")
// .attr("text-anchor", "end")
// .style("font-size", "6px")
// .style("text-decoration", "underline")
// .text("2nd TVD");
///////// change this!!!!!
if(title !== "" && header_sep_svg_or_not == "yes"){
let distance_from_top = -15
svg_header.append("text") //
.attr("x", (margin.left/3+(width/2)))
.attr("y", 0 + (- distance_from_top))
.attr("text-anchor", "middle")
.style("font-size", template_overall["title"]["title_font_size"])
.text(title);
}
const curveBox_main_div = d3.select("#"+div_id).append("div")
curveBox_main_div
.attr("height",height_components+"px")
.attr("class","component_outter")
.style('display','flex')
.style('position','relative')
.style('box-sizing','border-box')
const curveBox_sub_div = d3.select("#"+div_id+" div.component_outter").append("div")
curveBox_sub_div
.attr("class","component_inner")
.style('overflow-y',"auto")
.style('position','absolute')
.style('max-height',height_components+"px")
svg = d3.select("#"+div_id+" div.component_outter div.component_inner").append("svg")
}
else{
svg = d3.select("#"+div_id).append("svg")
}
////////////// Calculate Axis & Scales =>//////////////
//// Need to handle: zero curves, arbitrary number of curves, and min/max of all curves in single axis.
//// For zero curves, need to look into rectange and lines for x scales maybe?
//// Need to handle scales in linear, log, or arbitary user-provided scale.
//// Also, need x function for gridlines! so....?
////////////// Calculate x domain extent for one or more than one curve, used in scaling =>//////////////
let mins = []
let maxes = []
for (let i = 0; i < curve_names.length; i++) {
let min_this = d3.min(data, function(d) { return +d[curve_names[i]]})
let max_this = d3.max(data, function(d) { return +d[curve_names[i]]})
mins.push(min_this)
maxes.push(max_this)
}
let min_all_curves = d3.min(mins)
let max_all_curves = d3.max(maxes)
if (curve_names.length == 0){
//// THIS NEEDS TO CHANGE TO LOOK AT RECTANGLE AT SOME POINT!!!!!!
min_all_curves = 0
max_all_curves = 100
}
////////////// Calculate x domain extent for one or more than one curve, used in scaling =>//////////////
let x_func
let x
let xAxis_header
let xAxis
if (flag_for_single_scale_or_independent_scales = "single"){
for (let i = 0; i < curve_names.length; i++) {
let min_this = d3.min(data, function(d) { return +d[curve_names[i]]})
let max_this = d3.max(data, function(d) { return +d[curve_names[i]]})
mins.push(min_this)
maxes.push(max_this)
}
min_all_curves = d3.min(mins)
max_all_curves = d3.max(maxes)
x = d3.scaleLinear().domain([min_all_curves,max_all_curves]).nice().range([margin.left, width - margin.right])
if(scale_linear_log_or_yours == "log"){
x = d3.scaleLog().domain([min_all_curves,max_all_curves]).nice().range([margin.left, width - margin.right])
}
else if(scale_linear_log_or_yours == "linear"){}
else if(typeof(scale_linear_log_or_yours) !== "string"){
x = scale_linear_log_or_yours["yours"]
}
xAxis_header = g => g.attr("transform", "translate(0,45)").call(d3.axisBottom(x).ticks((width-margin.left-margin.right)/25).tickSizeOuter(0))
}
//////////////////// define y scale, aka the one for the depth ////////////////////
// let y = d3.scaleLinear().domain([depth_max, depth_min]).nice().range([height - margin.bottom,margin.top])
let y = d3.scaleLinear().domain([depth_max, depth_min]).range([height - margin.bottom,margin.top])
//let yAxis = g => g.attr("transform", `translate(${margin.left},0)`).call(d3.axisLeft(y)).call(g => g.select(".domain").remove())
let yAxis = g => g.attr("transform", `translate(${margin.left},0)`).call(d3.axisLeft(y)).call(g => g.select(".domain"))
let yAxis2 = g => g.attr("transform", `translate(${margin.left-35},0)`).call(d3.axisLeft(y)).call(g => g.select(".domain"))
////////////// Building curvebox parts that aren't header. First define size & title =>//////////////
svg.attr("class","components")
svg.attr("width",width)
.attr("height",height);
svg.style("margin","0 auto");
svg.style('overflow',"scroll")
if (header_sep_svg_or_not == "no"){
let xAxis = xAxis_header
svg.append("g")
.call(xAxis)
.append("text")
.text("test svg")
}
let y_axis_text = depth_curve_name+" "+depth_units_string+" "+depth_type_string
svg.append("g")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("dy", ".75em")
.attr("y", 0 - (margin.left*0.6))
.attr("x",((height)/-2)+margin.top)
.style("text-anchor", "end")
.text(y_axis_text)
.style("fill","#2b2929")
////
// svg.append("g")
// .call(yAxis2)
// .append("text")
// .attr("transform", "rotate(-90)")
// .attr("dy", ".75em")
// .attr("y", -35)
// .attr("x",((height)/-2)+margin.top)
// .style("text-anchor", "end")
// .text(y_axis_text+"THIS IS THE SECOND ONE")
// .style("fill","#2b2929")
////
//svg.append("g")
//// Code that assumes multiple curves are plotted in same curvebox
let distance_from_top = 40
if(title !== "" && header_sep_svg_or_not == "no"){
svg.append("text") //
.attr("x", (margin.left/3+(width/2)))
.attr("y", 0 + (- distance_from_top))
.attr("text-anchor", "middle")
.style("font-size", template_overall["title"]["title_font_size"])
.text(title);
}
if(gridlines == "yes"){
var gridlines_obj = d3.axisTop()
.ticks((width-margin.left-margin.right)/25)
.tickFormat("")
.tickSize(-height+margin.top+10)
.scale(x);
svg.append("g")
.attr("class", "grid")
.call(gridlines_obj)
.style("stroke",gridlines_color)
.style("stroke-width",gridlines_stroke_width)
}
// //// This will help save the x axis function for first curve if there are more than one curve
// /// and they are at different scales. We need this in order to use the 'between' method of fill!
// let x_for_k_is_0
// //// This will help save the x axis function for second curve if there are more than one curve
// /// and they are at different scales. We need this in order to use the 'between' method of fill!
// let x_for_k_is_1
// //// This will help save the x axis function for third curve if there are more than one curve
// /// and they are at different scales. We need this in order to use the 'between' method of fill!
// let x_for_k_is_2
// //// This will help save the x axis function for fourth curve if there are more than one curve
// /// and they are at different scales. We need this in order to use the 'between' method of fill!
// let x_for_k_is_3
let x_functions_for_each_curvename = {} //// populate with {"curvename":curvename,"x_func":func}
////////////// Building curves within curvebox =>//////////////
for (let k = 0; k < curve_names.length; k++) {
//// code that creates a line for each Curve in order provided and applies
//// the color in the color array in order provided
let curveUnit = "";
if (curve_units[k]){curveUnit = curve_units[k]}
let min = mins[k]
let max = maxes[k]
let header_text_line = min.toFixed(1)+" - "+curve_names[k]+" "+curveUnit+" - "+max.toFixed(1)
let min_this = d3.min(data, function(d) { return +d[curve_names[k]]})
let max_this = d3.max(data, function(d) { return +d[curve_names[k]]})
let x = d3.scaleLinear().domain([min_this,max_this]).nice().range([margin.left, width - margin.right])
if(scale_linear_log_or_yours == "log"){
x = d3.scaleLog().domain([min_this,max_this]).nice().range([margin.left, width - margin.right])
}
else if(scale_linear_log_or_yours == "linear"){}
else if(typeof(scale_linear_log_or_yours) !== "string"){
x = scale_linear_log_or_yours["yours"]
}
if(k==0){
x_func == x
}
//// This creates an object to hold multiple x axis scale functions
//// that will be used if 'between' style fill is selected.
x_functions_for_each_curvename[curve_names[k]] = x
////////////// Header text, two way depending on =>//////////////
if (header_sep_svg_or_not == "yes"){
let distance_from_top = (1+(k*2.7)).toString()+"em"
svg_header.append("text")
.attr("x", (margin.left+width)/2)
.attr("y", 0 + distance_from_top)
.attr("text-anchor", "middle")
.style("font-size", "11px")
.style("text-decoration", "underline")
.style('fill',curve_colors[k])
.text(header_text_line);
let translate_string = "translate(0,"+(45-(30*k)).toString()+")"
xAxis_header = g => g.attr("transform", translate_string).call(d3.axisBottom(x).ticks((width-margin.left-margin.right)/25).tickSizeOuter(0))
svg_header.append("g")
.call(xAxis_header)
.append("text")
}
let another_line = d3.line().x(d => x(d[curve_names[k]])).y(d => y(d[depth_curve_name]));
////////////// Appends a curve line but doesn't include fill yet =>//////////////
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", curve_colors[k])
.attr("stroke-width", template_curves["stroke_width"][k])
.attr("stroke-linecap", template_curves["stroke_linecap"][k])
.attr("stroke-dasharray",curve_stroke_dasharray[k])
.attr("d", another_line);
}
////////////// define the area filled under the curve =>//////////////
for (let i = 0; i < template_curves["fill"].length; i++) {
//let i = k
if (template_curves["fill"][i]["fill"] == "yes"){
let number_colors = template_curves["fill"][i]["fill_colors"].length
let curve_name1 = template_curves["fill"][i]["curve_name"]
let threshold = -99999999
let fill_color = "gray"
for (let j = 0; j < number_colors; j++) {
let area1 = d3.area()
if (number_colors != 0){
threshold = template_curves["fill"][i]["cutoffs"][j]
fill_color = template_curves["fill"][i]["fill_colors"][j]
}
if(template_curves["fill"][i]["fill_direction"] == "left"){
let start_from_left = template_overall["margin"]["left"]
area1
.x1(d => x(d[curve_name1]))
.x0(d => start_from_left)
.defined(d => ((d[curve_name1])>threshold))
.y(d => y(d[depth_curve_name]));
}
if(template_curves["fill"][i]["fill_direction"] == "right"){
let start_from_right = template_overall["margin"]["right"]
let start_from_left = template_overall["margin"]["left"]
area1
.x1(d => width-start_from_right)
.defined(d => ((d[curve_name1])>threshold))
.x0(d => x(d[curve_name1]))
.y(d => y(d[depth_curve_name]));
}
if(template_curves["fill"][i]["fill_direction"] == "between"){
let between_2_curve = template_curves["fill"][i]["curve2"]
//// for through x_functions_for_each_curvename object and find the key that
//// matches between_2_curve which should be a curvename
//// get the x function for the second curve and the curve that is curvenames[k]
let second_curve_x_func = x_functions_for_each_curvename[between_2_curve]
let first_curve_x_func = x_functions_for_each_curvename[curve_name1]
area1
.x1(d => first_curve_x_func(d[curve_name1]))
.x0(d => second_curve_x_func(d[between_2_curve]))
// .defined(d => ((d[curve_name1])<=d[between_2_curve]))
.y(d => y(d[depth_curve_name]));
}
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area1)
.attr("stroke", "none")
.attr("fill",fill_color)
.attr("fill-opacity",0.8);
}
}
}
//////////////// TOOLTIP Part 1 ///////////////////
if(mouseover_yes_or_no == "no"){
console.log("mouseover_yes_or_no = 'no' so no mouseover or hover of depth or curve value will be shown")
}
else{
//// statements to make sure the mouseover_curvename is a present curve and if not use first curve
if(mouseover_curvename == "default"){mouseover_curvename = curve_names[0]}
else if (curve_names.includes(mouseover_curvename)){mouseover_curvename = mouseover_curvename}
else{mouseover_curvename = curve_names[0]}
//// statement to handle color of curve text and circle on hover
let curve_on_mouseover_color = curve_colors[0]
if(mouseover_color_or_default_which_is_curve_color != "default"){
curve_on_mouseover_color = mouseover_color_or_default_which_is_curve_color
}
//// appends start of mouseover rectange used for showing hover content
var focus = svg.append("g")
.style("display", "none");
var bisectDate = d3.bisector(function(d) { return d[depth_curve_name]; }).left; // **
//// function called to change hover style & contents when mouseover rectangle appended to svg svg
function mousemove() {
var y0 = y.invert(d3.mouse(this)[1]),
i = bisectDate(data, y0, 1),
d0 = data[i - 1],
d1 = data[i],
d = y0 - d0[depth_curve_name] > d1[depth_curve_name] - y0 ? d1 : d0;
//// fixed value along y axis
let fixed_x_value = width*0.8
//// depth value
focus.select("text.y2")
.attr("transform",
"translate(" + fixed_x_value + "," +
y(d[depth_curve_name]) + ")")
.text(d[depth_curve_name]);
//// curve value
focus.select("text.y4")
.attr("transform",
// "translate(" + x(d[mouseover_curvename]) + "," +
"translate(" + fixed_x_value + "," +
y(d[depth_curve_name]) + ")")
.text(d[curve_names[0]]);
focus.select(".x")
.attr("transform",
"translate(" + x(d[mouseover_curvename]) + "," + 0+
")")
.attr("y2", height);
//// circle y class part 2
focus.select(".y")
.attr("transform",
"translate(" + x(d[mouseover_curvename]) + "," +
y(d[depth_curve_name]) + ")")
.text(d[mouseover_curvename]);
focus.select(".yl")
.attr("transform",
"translate(" + 0 + "," +
y(d[depth_curve_name]) + ")")
.text(d[mouseover_curvename]);
}
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", width);
// append the y line
focus.append("line")
.attr("class", "yl")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("x1", 0)
.attr("x2", height);
// append the circle at the intersection
focus.append("circle")
.attr("class", "y")
.style("fill", "none")
.style("stroke", curve_on_mouseover_color)
.attr("r", 3);
//// depth value on hover
if(mouseover_depth_or_depth_and_curve == "depth" || mouseover_depth_or_depth_and_curve == "depth_and_curve"){
focus.append("text")
.attr("class", "y2")
.attr("dx", 6)
.attr("dy", "-.3em")
.style("font-size","0.55em")
}
//// curve value on hover
if(mouseover_depth_or_depth_and_curve == "curve" || mouseover_depth_or_depth_and_curve == "depth_and_curve"){
focus.append("text")
.attr("class", "y4")
.attr("dx", 1)
.attr("dy", "0.5em")
.style("font-size","0.55em")
.style("fill", "black")
.style("stroke", curve_on_mouseover_color)
.style("stroke-width", "0.5px");
}
// append the rectangle to capture mouse // **********
svg.append("rect") // **********
.attr("width", width) // **********
.attr("height", height) // **********
.style("fill", "none") // **********
.style("pointer-events", "all") // **********
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove); // **********
}
////////////// Horizontal Lines AKA tops =>//////////////
try {
for (let i = 0; i < template_lines.length; i++) {
let this_line = template_lines[i]
svg.append("line")
.attr("x1", 0+margin.left)
.attr("y1", y(this_line["depth"]))
.attr("x2", width*0.75)
.attr("y2", y(this_line["depth"]))
.style("stroke-width", this_line["stroke_width"])
.style("stroke", this_line["color"])
.style("stroke-dasharray", this_line["stroke-dasharray"])
.style("stroke-linecap", this_line["stroke_linecap"])
.style("fill", "none");
svg.append("text")
.attr("x", width*0.75)
.attr("y", y(this_line["depth"]))
.attr("text-anchor", "start")
.style("font-size", "12px")
.text(this_line["label"]);
}
}
catch{
console.log("could not do lines for tops in curveBox function")
}
////////////// Rectangles for things like cores & sample locations =>//////////////
try {
for (let i = 0; i < template_rectangles.length; i++) {
let this_rectangle = template_rectangles[i]
let rectangle_bottom = this_rectangle.depth_top+this_rectangle.height
svg.append('rect')
.attr("x", 50+margin.left)
.attr("y", y(this_rectangle.depth_top))
.attr("width", this_rectangle.width)
.attr("y2",y(rectangle_bottom))
.style("stroke-width", this_rectangle.stroke_width)
.style("stroke-linecap", this_rectangle.stroke_linecap)
.style("stroke", "purple")
.style("fill", this_rectangle.fill)
.style("opacity", this_rectangle.opacity);
svg.append("text")
.attr("x", width*0.75)
.attr("y", y(this_rectangle.depth_top))
.attr("text-anchor", "start")
.style("font-size", "12px")
.text(this_rectangle.label);
}
}
catch{
console.log("could not do rectangle in curveBox function for some reason")
}
////////////// Calling node. Only returning svg node for saving single SVG file purposes =>//////////////
svg_holder.node()
svg_header.node()
return svg.node();
}