/* By Bo Ericsson, https://www.linkedin.com/in/boeric00/ */ /* eslint-disable no-multi-spaces, operator-linebreak, indent, no-plusplus, no-nested-ternary, func-names */ /* global d3 */ /* Please note that indentation rules follow D3 praxis, that is, any method that alters the selection is indented with two spaces, and all other methods are indented with four spaces. There is no easy eslint rule to fix this... */ const itemCount = 9; const data = []; // Body dimensions const bodyMargin = 0; const bodyWidth = 940; // To fit https://bl.ocks.org width requirement const bodyHeight = 480; // To fit https://bl.ocks.org height requirement const bodyPadding = 10; // Title and subtitle container height const titleHeight = 20; // Controls container dimensions const controlsHeight = 20; const controlsMarginBottom = 30; // Content container dimensions const contentContainerMarginTop = 10; const contentContainerMaxHeight = bodyHeight - // Total height of body titleHeight - // Height of title container controlsHeight - controlsMarginBottom - // Total height of controls container titleHeight - // Height of subtitle contentContainerMarginTop; // Content container top margin // Set body dimensions d3.select('body') .style('margin', `${bodyMargin}px`) .style('height', `${bodyHeight}px`) .style('width', `${bodyWidth}px`) .style('padding', `${bodyPadding}px`); // Item dimensions const width = 40; const height = 15; const margin = 10; const border = 3; const padding = 8; const outline = 1; // Create data for (let i = 0; i < itemCount; i++) { data.push(`Item ${i}`); } // Options for the select control const justifyContentOptions = [ { value: 'space-evenly', default: false, idx: 0 }, { value: 'space-between', default: true, idx: 1 }, { value: 'space-around', default: false, idx: 2 }, { value: 'center', default: false, idx: 3 }, { value: 'flex-end', default: false, idx: 4 }, { value: 'flex-start', default: false, idx: 5 }, ]; // Set initial flexbox direction let direction = 'row'; // Set default flexbox direction const defaultJustifyContent = justifyContentOptions.find(d => d.default); const defaultJustifyIdx = defaultJustifyContent.idx; let justifyContent = defaultJustifyContent.value; // Define the input controls const controls = { useMargin: { type: 'checkbox', pos: 0, label: 'Use Margin', checked: true, title: 'Use Margin', }, useBorder: { type: 'checkbox', pos: 1, label: 'Use Border', checked: true, title: 'Use Border', }, usePadding: { type: 'checkbox', pos: 2, label: 'Use Padding', checked: true, title: 'Use Porder', }, useOutline: { type: 'checkbox', pos: 3, label: 'Use Outline', checked: true, title: 'Use Outline', }, useScroll: { type: 'checkbox', pos: 4, label: 'Overflow scroll', checked: true, title: 'Scroll or hide overflow', }, useMaxSize: { type: 'checkbox', pos: 5, label: 'Max Container Size', checked: true, title: 'Use Max Size of Container', }, flexRow: { type: 'radio', pos: 6, label: 'Row', checked: direction === 'row', title: 'Row Direction', }, flexColumn: { type: 'radio', pos: 7, label: 'Column', checked: direction === 'column', title: 'Column Direction', }, useFlex: { type: 'checkbox', pos: 8, label: 'Use Flexbox', checked: true, title: 'Use Flex', }, }; // Update the visualization function updateViz() { // Update container const flex = controls.useFlex.checked; const maxSize = controls.useMaxSize.checked; const scroll = controls.useScroll.checked; // Compute dimensions const marginUsed = controls.useMargin.checked ? margin : null; const borderUsed = controls.useBorder.checked ? border : null; const paddingUsed = controls.usePadding.checked ? padding : null; const outlineUsed = controls.useOutline.checked ? outline : null; // Generate dimension strings const totalMargin = (marginUsed || 0) * 2; const totalBorder = (borderUsed || 0) * 2; const totalPadding = (paddingUsed || 0) * 2; const totalWidth = width + totalPadding + totalBorder + totalMargin; const totalHeight = height + totalPadding + totalBorder + totalMargin; const coreStr = `Core: ${width}px/${height}px`; const marginStr = `Margin: ${totalMargin}px`; const borderStr = `Border: ${totalBorder}px`; const paddingStr = `Padding: ${totalPadding}px`; const totalStr = `Total: ${totalWidth}px/${totalHeight}px`; const dimStr = `${coreStr}, ${paddingStr}, ${borderStr}, ${marginStr}, ${totalStr}`; // Update content container const contentContainer = d3.select('.contentContainer') .style('display', flex ? 'flex' : 'inline-block') // .style('display', flex ? 'flex' : 'inline') .style('flex-direction', flex ? direction : null) .style('justify-content', flex ? justifyContent : null) .style('width', maxSize ? '100%' : 'auto') .style('height', maxSize ? `${contentContainerMaxHeight}px` : null) .style('overflow', scroll ? 'scroll' : 'hidden'); // Determine selection const updateSelection = contentContainer.selectAll('span').data(data); const enterSelection = updateSelection.enter(); const enterSelectionSize = enterSelection.size(); const selection = enterSelectionSize === 0 ? updateSelection : enterSelection.append('span'); // Update items selection .attr('class', 'innerElem') .style('margin', marginUsed !== null ? `${marginUsed}px` : null) .style('border', borderUsed !== null ? `${borderUsed}px solid #2196f3` : null) .style('padding', paddingUsed !== null ? `${paddingUsed}px` : null) .style('outline', outlineUsed !== null ? `${outlineUsed}px solid red` : null) .style('width', `${width}px`) .style('height', `${height}px`) .style('display', direction === 'row' ? 'inline-block' : 'block') .text((d) => d); // Update overlay const overlay = d3.select('.overlay'); overlay.selectAll('*').remove(); const overlayItem = overlay .append('div') .style('display', 'flex') .style('flex-direction', 'row'); // Add an outer wrapper to the 'Item' that will be added below const itemContainer = overlayItem .append('div') .style('outline', '1px solid orange') .style('display', 'inline-block'); // Add an item instance inside the item container itemContainer.append('span') .attr('class', 'innerElem') .style('margin', marginUsed !== null ? `${marginUsed}px` : null) .style('border', borderUsed !== null ? `${borderUsed}px solid #2196f3` : null) .style('padding', paddingUsed !== null ? `${paddingUsed}px` : null) .style('outline', outlineUsed !== null ? `${outlineUsed}px solid red` : null) .style('width', `${width}px`) .style('height', `${height}px`) .style('display', direction === 'row' ? 'inline-block' : 'block') .text('Item'); // Add text to the right of the item overlayItem.append('div') .style('margin-left', '10px') .text('Orange rectangle represents total dimension'); const infoContainer = overlay.append('div') .attr('class', 'infoContainer'); const itemDimensionContainer = infoContainer.append('div') .attr('class', 'textBold') .style('margin-top', '10px') .text('Item Dimensions'); itemDimensionContainer.append('div') .attr('class', 'textNormal') .style('margin-left', '10px') .text(dimStr); const itemStyleContainer = infoContainer.append('div') .attr('class', 'textBold') .style('margin-top', '10px') .text('Item Inline Styles'); itemStyleContainer.append('div') .attr('class', 'textNormal') .style('margin-left', '10px') .text(selection.nodes()[0].style.cssText); const contentStyleContainer = infoContainer.append('div') .attr('class', 'textBold') .style('margin-top', '10px') .text('Container Inline Styles'); contentStyleContainer.append('div') .attr('class', 'textNormal') .style('margin-left', '10px') .text(contentContainer.nodes()[0].style.cssText); } // Set dimensions of the container that contains the input controls (checkboxes, radios and select) const controlsContainer = d3.select('.controlsContainer') .style('height', `${controlsHeight}px`) .style('margin-bottom', `${controlsMarginBottom}px`); // Create span elem wrapper for each input control const keys = Object.keys(controls).sort((a, b) => (a.pos > b.pos ? 1 : a.pos < b.pos ? -1 : 0)); const spans = controlsContainer.selectAll('span') .data(keys) .enter() .append('span') .style('margin-right', '5px'); // Create checkboxes and radios, and add event handler spans.append('input') .attr('type', (d) => controls[d].type) .attr('name', (d) => d) .attr('title', (d) => controls[d].title) .property('checked', (d) => controls[d].checked) .on('change', function(d) { const checked = d3.select(this).property('checked'); // Manage radios without form const radios = d3.selectAll('input[type="radio"]'); switch (d) { case 'useFlex': controls[d].checked = checked; // Disable select if flexbox is not used d3.select('.justifySelect').property('disabled', !checked); break; case 'flexRow': direction = 'row'; controls.flexRow.checked = true; controls.flexColumn.checked = false; // Update the radios radios.nodes()[0].checked = true; radios.nodes()[1].checked = false; break; case 'flexColumn': direction = 'column'; controls.flexRow.checked = false; controls.flexColumn.checked = true; // Update the radios radios.nodes()[0].checked = false; radios.nodes()[1].checked = true; break; default: controls[d].checked = checked; } // Update content updateViz(data); }); // Create labels for the checkboxes and radios spans.append('label') .attr('for', (d) => d) .attr('title', (d) => controls[d].title) .text((d) => controls[d].label); // Create select control const selectControl = controlsContainer.append('select') .attr('class', 'justifySelect') .on('change', function () { const option = d3.select(this).node().value; justifyContent = option; updateViz(); }); // Add options to select selectControl.selectAll('option') .data(justifyContentOptions) .enter() .append('option') .text((d) => d.value); // Set default (pre-preselected value) of select selectControl.property('selectedIndex', defaultJustifyIdx); // Add height to title container d3.select('.titleContainer') .style('height', `${titleHeight}px`); // Add height to subtitle container d3.select('.subTitleContainer') .style('height', `${titleHeight}px`); // Set top margin and background color of content container d3.select('.contentContainer') .style('margin-top', `${contentContainerMarginTop}px`); // Initial update updateViz();