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