From b8e1478bab9da651a73fdf7b80b7f2af02e03d9c Mon Sep 17 00:00:00 2001
From: Frank Elavsky <frankelavsky@gmail.com>
Date: Fri, 5 Jul 2024 08:30:57 -0400
Subject: [PATCH] add chart and trim options

---
 early_explo/examples/menu/chart.css |  46 +++++++
 early_explo/examples/menu/chart.js  | 186 ++++++++++++++++++++++++++++
 early_explo/examples/menu/menu.css  |  64 +++++++++-
 early_explo/examples/menu/menu.html |  52 +++++---
 early_explo/examples/menu/menu.js   | 119 ++++++++++++------
 early_explo/examples/menu/props.js  | 127 +++++++++++++++++++
 6 files changed, 541 insertions(+), 53 deletions(-)
 create mode 100644 early_explo/examples/menu/chart.css
 create mode 100644 early_explo/examples/menu/chart.js
 create mode 100644 early_explo/examples/menu/props.js

diff --git a/early_explo/examples/menu/chart.css b/early_explo/examples/menu/chart.css
new file mode 100644
index 0000000..f55c586
--- /dev/null
+++ b/early_explo/examples/menu/chart.css
@@ -0,0 +1,46 @@
+#csv {
+    display: none;
+}
+
+.highcharts-figure,
+.highcharts-data-table table {
+    min-width: 310px;
+    max-width: 800px;
+    margin: 1em auto;
+}
+
+.highcharts-data-table table {
+    font-family: Verdana, sans-serif;
+    border-collapse: collapse;
+    border: 1px solid #ebebeb;
+    margin: 10px auto;
+    text-align: center;
+    width: 100%;
+    max-width: 500px;
+}
+
+.highcharts-data-table caption {
+    padding: 1em 0;
+    font-size: 1.2em;
+    color: #555;
+}
+
+.highcharts-data-table th {
+    font-weight: 600;
+    padding: 0.5em;
+}
+
+.highcharts-data-table td,
+.highcharts-data-table th,
+.highcharts-data-table caption {
+    padding: 0.5em;
+}
+
+.highcharts-data-table thead tr,
+.highcharts-data-table tr:nth-child(even) {
+    background: #f8f8f8;
+}
+
+.highcharts-data-table tr:hover {
+    background: #f1f7ff;
+}
\ No newline at end of file
diff --git a/early_explo/examples/menu/chart.js b/early_explo/examples/menu/chart.js
new file mode 100644
index 0000000..8549d4d
--- /dev/null
+++ b/early_explo/examples/menu/chart.js
@@ -0,0 +1,186 @@
+console.log("bork")
+const propNameMap = {
+    title: "Title",
+    subtitle: "Subtitle",
+    tooltip: "Tooltip"
+}
+const propValueMap = {
+    fontSize: {
+        small: "8pt",
+        "small+": "10pt",
+        medium: "12pt",
+        "medium+": "14pt",
+        large: "16pt"
+    }
+}
+const parseProps = (highchartsPropName) => {
+    let styleObject = {}
+    let propName = propNameMap[highchartsPropName]
+    console.log(highchartsPropName)
+    props.Text["Font Size"].Title.value ? propMap["Font Size"][props.Text["Font Size"].Title.value] : undefined
+    if (props.Text["Font Size"][propName].value) {
+        styleObject.fontSize = propValueMap.fontSize[props.Text["Font Size"][propName].value]
+    }
+    console.log(styleObject)
+    return styleObject
+}
+Highcharts.chart('container', {
+    title: {
+        text: 'Estimated US Energy Consumption in 2017',
+        style: parseProps("title")
+    },
+    subtitle: {
+        text:
+      'Source: <a href=\'https://www.llnl.gov/\'> Lawrence Livermore National Laboratory</a>',
+      style: parseProps("subtitle")
+    },
+    accessibility: {
+        point: {
+            valueDescriptionFormat: '{index}. {point.from} to {point.to}, ' +
+                '{point.weight}.'
+        }
+    },
+    tooltip: {
+        headerFormat: null,
+        pointFormat:
+      '{point.fromNode.name} \u2192 {point.toNode.name}: {point.weight:.2f} ' +
+      'quads',
+        nodeFormat: '{point.name}: {point.sum:.2f} quads',
+        style: parseProps("tooltip")
+    },
+    series: [{
+        keys: ['from', 'to', 'weight'],
+        nodes: [
+            {
+                id: 'Electricity & Heat',
+                color: '#ffa500',
+                offset: -110
+            },
+            {
+                id: 'Residential',
+                color: '#74ffe7',
+                column: 2,
+                offset: 50
+            },
+            {
+                id: 'Commercial',
+                color: '#8cff74',
+                column: 2,
+                offset: 50
+            },
+            {
+                id: 'Industrial',
+                color: '#ff8da1',
+                column: 2,
+                offset: 50
+            },
+            {
+                id: 'Transportation',
+                color: '#f4c0ff',
+                column: 2,
+                offset: 50
+            },
+            {
+                id: 'Rejected Energy',
+                color: '#e6e6e6',
+                column: 3,
+                offset: -30
+            },
+            {
+                id: 'Energy Services',
+                color: '#F9E79F',
+                column: 3
+            },
+            {
+                id: 'Solar',
+                color: '#009c00'
+            },
+            {
+                id: 'Nuclear',
+                color: '#1a8dff'
+            },
+            {
+                id: 'Hydro',
+                color: '#009c00'
+            },
+            {
+                id: 'Wind',
+                color: '#009c00'
+            },
+            {
+                id: 'Geothermal',
+                color: '#009c00'
+            },
+            {
+                id: 'Natural Gas',
+                color: '#1a8dff'
+            },
+            {
+                id: 'Biomass',
+                color: '#009c00'
+            },
+            {
+                id: 'Coal',
+                color: '#989898'
+            },
+            {
+                id: 'Petroleum',
+                color: '#989898',
+                offset: -1
+            }
+        ],
+        data: [
+            ['Solar', 'Electricity & Heat', 0.48],
+            ['Nuclear', 'Electricity & Heat', 8.42],
+            ['Hydro', 'Electricity & Heat', 2.75],
+            ['Wind', 'Electricity & Heat', 2.35],
+            ['Geothermal', 'Electricity & Heat', 0.15],
+            ['Natural Gas', 'Electricity & Heat', 9.54],
+            ['Coal', 'Electricity & Heat', 12.7],
+            ['Biomass', 'Electricity & Heat', 0.52],
+            ['Petroleum', 'Electricity & Heat', 0.21],
+
+            ['Electricity & Heat', 'Residential', 4.7],
+            ['Solar', 'Residential', 0.19],
+            ['Geothermal', 'Residential', 0.04],
+            ['Natural Gas', 'Residential', 4.58],
+            ['Biomass', 'Residential', 0.33],
+            ['Petroleum', 'Residential', 0.88],
+
+            ['Electricity & Heat', 'Commercial', 4.6],
+            ['Solar', 'Commercial', 0.08],
+            ['Geothermal', 'Commercial', 0.02],
+            ['Natural Gas', 'Commercial', 3.29],
+            ['Coal', 'Commercial', 0.02],
+            ['Biomass', 'Commercial', 0.16],
+            ['Petroleum', 'Commercial', 0.83],
+
+            ['Electricity & Heat', 'Industrial', 3.23],
+            ['Solar', 'Industrial', 0.02],
+            ['Hydro', 'Industrial', 0.01],
+            ['Natural Gas', 'Industrial', 9.84],
+            ['Coal', 'Industrial', 1.24],
+            ['Biomass', 'Industrial', 2.48],
+            ['Petroleum', 'Industrial', 8.38],
+
+            ['Electricity & Heat', 'Transportation', 0.03],
+            ['Natural Gas', 'Transportation', 0.76],
+            ['Biomass', 'Transportation', 1.43],
+            ['Petroleum', 'Transportation', 25.9],
+
+            ['Electricity & Heat', 'Rejected Energy', 24.7],
+            ['Residential', 'Rejected Energy', 3.75],
+            ['Commercial', 'Rejected Energy', 3.15],
+            ['Industrial', 'Rejected Energy', 12.9],
+            ['Transportation', 'Rejected Energy', 22.2],
+
+            ['Residential', 'Energy Services', 6.97],
+            ['Commercial', 'Energy Services', 5.84],
+            ['Industrial', 'Energy Services', 12.4],
+            ['Transportation', 'Energy Services', 5.91]
+        ],
+        type: 'sankey',
+        name: 'Sankey demo series'
+    }]
+
+});
\ No newline at end of file
diff --git a/early_explo/examples/menu/menu.css b/early_explo/examples/menu/menu.css
index 509248e..c5ca736 100644
--- a/early_explo/examples/menu/menu.css
+++ b/early_explo/examples/menu/menu.css
@@ -1,5 +1,9 @@
 body {
-    padding: 3em;
+    padding: 2vh 3vw;
+}
+
+h1 {
+    margin-left:0.5em;
 }
 
 h1, h2, h3, h4, h5 h6 {
@@ -20,7 +24,6 @@ legend {
 }
 
 details, .highcharts-empty-details {
-    padding-bottom: 0.5em;
     padding-left: 1em;
 }
 
@@ -29,8 +32,9 @@ details, .highcharts-empty-details {
 }
 
 summary, .highcharts-empty-summary {
-    padding-bottom: 0.25em;
+    padding-bottom: 0.5em;
     padding-top: 0.375em;
+    padding-left: 0.5em;
 }
 
 .highcharts-menu-column {
@@ -145,4 +149,58 @@ details > summary::before {
 details[open] > summary::before {
     content: '\25BC';
     padding-right: 0;
+}
+
+summary {
+    border: 1px solid #ffffff00;
+}
+
+summary:hover {
+    cursor: pointer;
+    background-color: #f9fdff;
+    border: 1px dashed #767676;
+    border-radius: 8px;
+}
+
+/* container */
+.highcharts-responsive-two-columns {
+    display:flex;
+    flex-wrap:wrap;
+}
+
+/* columns */
+.highcharts-responsive-two-columns > * {
+    width:100%;
+    padding:1rem;
+}
+
+/* tablet breakpoint */
+@media (min-width:1079px) {
+    .highcharts-responsive-column-menu {
+        width:32%;
+    }
+    .highcharts-responsive-column-chart {
+        width:62%;
+        height: 90vh;
+    }
+}
+
+#menu {
+    border: 1px solid #767676;
+    border-radius: 10px;
+    max-height: 90vh;
+    overflow: scroll;
+}
+
+input:hover {
+    cursor: pointer;
+}
+
+.highcharts-hiding-children .highcharts-hide-unavailable {
+    display: none;
+}
+
+.highcharts-hide-unavailable {
+    color: #767676;
+    font-weight: 100;
 }
\ No newline at end of file
diff --git a/early_explo/examples/menu/menu.html b/early_explo/examples/menu/menu.html
index 9aa4da3..3a7eeda 100644
--- a/early_explo/examples/menu/menu.html
+++ b/early_explo/examples/menu/menu.html
@@ -1,15 +1,37 @@
-<link rel="stylesheet" href="./menu.css">
-<script src="https://code.highcharts.com/highcharts.js"></script>
-<script src="https://code.highcharts.com/modules/exporting.js"></script>
-<script src="https://code.highcharts.com/modules/export-data.js"></script>
-<script src="https://code.highcharts.com/modules/accessibility.js"></script>
-
-<figure class="highcharts-figure">
-    <div id="menu"></div>
-    <div id="container"></div>
-    <p class="highcharts-description">
-        (chart goes here)
-    </p>
-</figure>
-
-<script src="./menu.js"></script>
\ No newline at end of file
+<!DOCTYPE html>
+<html>
+    <head>
+        <link rel="stylesheet" href="./menu.css">
+        <link rel="stylesheet" href="./chart.css">
+        <script src="https://code.highcharts.com/highcharts.js"></script>
+        <script src="https://code.highcharts.com/modules/sankey.js"></script>
+        <script src="https://code.highcharts.com/modules/exporting.js"></script>
+        <script src="https://code.highcharts.com/modules/export-data.js"></script>
+        <script src="https://code.highcharts.com/modules/accessibility.js"></script>
+    </head>
+
+    <!-- <figure class="highcharts-figure"> -->
+    <body>
+        <div>
+            <div class="highcharts-responsive-two-columns">
+                <div class="highcharts-responsive-column-menu highcharts-hiding-children" id="menu"></div>
+                <div class="highcharts-responsive-column-chart" id="container">
+                    <figure class="highcharts-figure">
+                        <div id="container"></div>
+                        <p class="highcharts-description">
+                            Sankey charts are used to visualize data flow and volume
+                            between nodes. The wider lines indicate larger volumes.
+                        </p>
+                    </figure>
+                </div>
+            </div>
+        </div>
+    </body>
+
+    <!-- </figure> -->
+
+    <script src="./props.js"></script>
+    <script src="./chart.js"></script>
+    <script src="./menu.js"></script>
+
+</html>
\ No newline at end of file
diff --git a/early_explo/examples/menu/menu.js b/early_explo/examples/menu/menu.js
index 1f2d719..647ef9d 100644
--- a/early_explo/examples/menu/menu.js
+++ b/early_explo/examples/menu/menu.js
@@ -29,6 +29,7 @@ const rawPrefs = `- Comprehension (no assistance, moderate, robust)
 - - - Subtitle (small, small+, medium, medium+, large)
 - - - Mark Labels (small, small+, medium, medium+, large)
 - - - Series Labels (small, small+, medium, medium+, large)
+- - - Tooltip (small, small+, medium, medium+, large)
 - - - Legend Title (small, small+, medium, medium+, large)
 - - - Legend Labels (small, small+, medium, medium+, large)
 - - - Axis Titles (small, small+, medium, medium+, large)
@@ -168,6 +169,7 @@ const rawPrefs = `- Comprehension (no assistance, moderate, robust)
 - - Tempo ticker (disabled, quiet, moderate, loud)
 - Motion (disabled, staged, slower, faster)
 - Interactivity (disabled, minimal feedback, high feedback)
+- - Pointer focus (disable, on hover, on click)
 - - Interaction console (hide, show as log, always show)
 - - Tooltips (disabled, show on focus, toggle only)
 - - Element selection (disabled, no confirmation, use confirmation)
@@ -185,56 +187,79 @@ const rawPrefs = `- Comprehension (no assistance, moderate, robust)
 - - - Previous across element (comma, custom keypress)
 - - - Parent element (AT default, up, page up, custom keypress)
 - - - First child element (AT default, down, page down, custom keypress)`
-var parsePreferences = (str) => {
+const parsePreferences = (str) => {
     const lines = str.trim().split('\n');
-    const stack = [];
     let root = [];
   
     lines.forEach(line => {
-      const indentLevel = (line.match(/-/g) || []).length;
-      if (!indentLevel) {console.log("FAILURE")}
-  
-      const cleanLine = line.replaceAll('- ',''); // Remove '- ' from the start
-  
-      const [name, optionsStr] = cleanLine.split(' (');
-  
-      const options = optionsStr ? optionsStr.slice(0, -1).split(', ') : [];
+        const indentLevel = (line.match(/-/g) || []).length;
+        if (!indentLevel) {console.log("FAILURE")}
+    
+        const cleanLine = line.replaceAll('- ',''); // Remove '- ' from the start
+    
+        const [name, optionsStr] = cleanLine.split(' (');
+    
+        const options = optionsStr ? optionsStr.slice(0, -1).split(', ') : [];
   
-      const newItem = {
-        name: name.trim(),
-        options: options,
-        children: []
-      };
+        let newItem = {
+            name: name.trim(),
+            domName: name.trim().toLowerCase().replace(/\s+/g, '-'),
+            options: options,
+            available: false,
+            children: []
+        };
   
-      if (indentLevel === 1) {
-          root.push(newItem);
-      } else {
-          const parent = root[root.length - 1];
-          // if (!parent.children) parent.children = [];
-          let target = parent;
-          if (indentLevel === 3) {
-              target = parent.children[parent.children.length - 1];
-          }
-          target.children.push(newItem)
-      }
+        if (indentLevel === 1) {
+            if (props[newItem.name] && props[newItem.name].available) {
+                newItem.available = true
+            } else {
+                props[newItem.name] = {
+                    available: false,
+                    value: "",
+                    enabled: false
+                }
+            }
+            root.push(newItem);
+        } else {
+            const parent = root[root.length - 1];
+            // if (!parent.children) parent.children = [];
+            let target = parent;
+            let propTarget = props[parent.name];
+            if (indentLevel === 3) {
+                target = parent.children[parent.children.length - 1];
+                propTarget = props[parent.name][parent.children[parent.children.length - 1].name]
+            }
+            if (propTarget.available && propTarget[newItem.name] && propTarget[newItem.name].available) {
+                newItem.available = true
+            } else {
+                propTarget[newItem.name] = {
+                    available: false,
+                    value: "",
+                    enabled: false
+                }
+            }
+            target.children.push(newItem)
+        }
     });
-  
+    console.log(props)
     return root;
-  }
+}
 const allPreferences = parsePreferences(rawPrefs)
+let allOptionsFlattened = {}
 function generatePreferencesHTML(preferences, level = 2, parentName = '') {
   let html = '';
   
   preferences.forEach(pref => {
     const headingTag = `h${level}`;
     const nextLevel = level + 1;
-    const inputName = pref.name.toLowerCase().replace(/\s+/g, '-');
+    const inputName = pref.domName;
     const children = pref.children && pref.children.length > 0
 
-    const details = children ? "details" : "div class='highcharts-empty-details'"
+    const unavailable = pref.available ? "" : "highcharts-hide-unavailable"
+    const details = children ? `details  class="${unavailable}"` : `div class="highcharts-empty-details ${unavailable}"`
     const summary = children ? "summary" : "div class='highcharts-empty-summary'"
 
-    html += `<${details}>
+    html += `<${details}">
         <${summary} class="highcharts-menu-group highcharts-menu-group-${level - 1}">
             <form>
                 <div class="highcharts-menu-column">
@@ -243,16 +268,23 @@ function generatePreferencesHTML(preferences, level = 2, parentName = '') {
                         <${headingTag}>${pref.name}<span class="highcharts-menu-hint highcharts-menu-hidden" aria-label="note: at least one child option overrides this setting.">*</span></${headingTag}>
                     </label>
                         
-                    <input class="highcharts-menu-checkbox" type="checkbox" name="${parentName+inputName}"/>
+                    <input class="highcharts-menu-checkbox" type="checkbox" name="${parentName+inputName}" ${unavailable ? 'disabled' : ''}/>
                     </div>
                     <div class="highcharts-menu-slider-line"></div>
                     <div id="${parentName+inputName}-menu" class="highcharts-menu-slider-wrapper highcharts-column-right highcharts-menu-slider-disabled">`;
                 
     pref.options.forEach((option, index) => {
         const optionName = `${parentName+inputName}-${option.toLowerCase().replace(/\s+/g, '-')}`;
+        allOptionsFlattened[optionName] = {
+            name: option,
+            domName: option.toLowerCase().replace(/\s+/g, '-'),
+            parent: pref,
+            parentName,
+            inputName
+        }
         html += `<div class="highcharts-menu-slider-option"><label for="${optionName}" data-value="${option.toLowerCase()}">
                 ${option}
-            </label><input type="radio" name="${parentName+inputName}" id="${optionName}" value="${index}" required="" disabled></div>`;
+            </label><input type="radio" name="${parentName+inputName}" id="${optionName}" class="highcharts-menu-radio" value="${index}" required="" disabled></div>`;
     });
                 
     html += `</div></div></form></${summary}>`;
@@ -267,7 +299,7 @@ function generatePreferencesHTML(preferences, level = 2, parentName = '') {
   return html;
 }
 
-let x = generatePreferencesHTML(allPreferences)
+let x = '<h1>Preferences</h1><div class="highcharts-empty-details">Hide unavailable options<input type="checkbox" class="highcharts-toggle-unavailable" checked></div>' + generatePreferencesHTML(allPreferences)
 
 document.getElementById('menu').innerHTML = x
 
@@ -302,7 +334,24 @@ const toggleOptions = (e) => {
         i++
     })
 }
+const toggleShowAvailable = (e) => {
+    e.srcElement.parentNode.parentNode.classList.toggle("highcharts-hiding-children")
+}
+const changeValue = (e) => {
+    console.log(e.srcElement)
+    console.log(e.srcElement.id,allOptionsFlattened[e.srcElement.id])
+    const option = allOptionsFlattened[e.srcElement.id]
+    const downstreamOptions = option.parent.children.length ? option.parent.children : [option.parent]
+    downstreamOptions.forEach(affectedOption => {
+        
+    })
+}
 const checkboxes = [...document.querySelectorAll(".highcharts-menu-checkbox")]
 checkboxes.forEach(box => {
     box.addEventListener("click",toggleOptions)
-})
\ No newline at end of file
+})
+document.querySelector(".highcharts-toggle-unavailable").addEventListener("click",toggleShowAvailable)
+const radios = [...document.querySelectorAll(".highcharts-menu-radio")]
+radios.forEach(radio => {
+    radio.addEventListener("click",changeValue)
+})
diff --git a/early_explo/examples/menu/props.js b/early_explo/examples/menu/props.js
new file mode 100644
index 0000000..4d92740
--- /dev/null
+++ b/early_explo/examples/menu/props.js
@@ -0,0 +1,127 @@
+let props = {
+    Comprehension: {
+        value: "",
+        available: true,
+        enabled: false,
+        "Alt text appearance": {
+            value: "",
+            available: true,
+            enabled: false
+        }
+    },
+    Text: {
+        value: "",
+        available: true,
+        enabled: false,
+        "Font Size": {
+            value: "",
+            available: true,
+            enabled: false,
+            Title: {
+                value: "",
+                available: true,
+                enabled: false
+            },
+            Subtitle: {
+                value: "",
+                available: true,
+                enabled: false
+            },
+            Tooltip: {
+                value: "",
+                available: true,
+                enabled: false
+            },
+            seriesLabel: {
+                value: "",
+                available: true,
+                enabled: false
+            }
+        },
+        "Font Weight": {
+            value: "",
+            available: true,
+            enabled: false,
+            title: {
+                value: "",
+                available: true,
+                enabled: false
+            },
+            subtitle: {
+                value: "",
+                available: true,
+                enabled: false
+            },
+            tooltip: {
+                value: "",
+                available: true,
+                enabled: false
+            },
+            seriesLabel: {
+                value: "",
+                available: true,
+                enabled: false
+            }
+        }
+    },
+    "Color and contrast": {
+        value: "",
+        available: true,
+        enabled: false,
+        "Text color": {
+            value: "",
+            available: true,
+            enabled: false
+        },
+        "Mark color": {
+            value: "",
+            available: true,
+            enabled: false
+        },
+        "Distinguish without color": {
+            value: "",
+            available: true,
+            enabled: false,
+            "Fill patterns": {
+                value: "",
+                available: true,
+                enabled: false
+            }
+        }
+    },
+    "Element size": {
+        value: "",
+        available: true,
+        enabled: false,
+        Lines: {
+            value: "",
+            available: true,
+            enabled: false,
+            Outlines : {
+                value: "",
+                available: true,
+                enabled: false
+            }
+        }
+    },
+    Audio: {
+        value: "",
+        available: false,
+        enabled: false
+    },
+    Motion: {
+        value: "",
+        available: true,
+        enabled: false
+    },
+    Interactivity: {
+        value: "",
+        available: true,
+        enabled: false,
+        "Pointer focus": {
+            value: "",
+            available: true,
+            enabled: false
+        }
+    }
+}
\ No newline at end of file