Skip to content

Commit

Permalink
feat(#135): sort boxes by parent first (#138)
Browse files Browse the repository at this point in the history
* refactor(#135): refactor to allow sorting by parent

* feat(#135): sort boxes by parent first
  • Loading branch information
MindFreeze authored Oct 18, 2023
1 parent 19f89e5 commit 73edbdb
Showing 1 changed file with 101 additions and 96 deletions.
197 changes: 101 additions & 96 deletions src/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export class Chart extends LitElement {
if (!this.config) {
return false;
}
if (changedProps.has('config') || changedProps.has('forceUpdateTs') || changedProps.has('highlightedEntities') || changedProps.has('zoomEntity')) {
if (
changedProps.has('config') ||
changedProps.has('forceUpdateTs') ||
changedProps.has('highlightedEntities') ||
changedProps.has('zoomEntity')
) {
return true;
}
const now = Date.now();
Expand Down Expand Up @@ -222,111 +227,111 @@ export class Chart extends LitElement {
private _calcBoxes() {
this.statePerPixelY = 0;
const filteredConfig = filterConfigByZoomEntity(this.config, this.zoomEntity);
this.sections = filteredConfig.sections
.map(section => {
let total = 0;
const boxes: Box[] = section.entities
.filter(entityConf => {
const { min_state } = this.config;
// remove empty entity boxes
if (entityConf.type === 'remaining_parent_state') {
return this.connectionsByChild.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
if (entityConf.type === 'remaining_child_state') {
return this.connectionsByParent.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
const { state } = this._getMemoizedState(entityConf);
return state && state >= min_state;
})
.map(entityConf => {
const { state, unit_of_measurement } = this._getMemoizedState(entityConf);
total += state;

let finalColor = entityConf.color || 'var(--primary-color)';
if (typeof entityConf.color_on_state != 'undefined' && entityConf.color_on_state) {
const colorLimit = typeof entityConf.color_limit === 'undefined' ? 1 : entityConf.color_limit;
const colorBelow =
typeof entityConf.color_below === 'undefined' ? 'var(--primary-color)' : entityConf.color_below;
const colorAbove =
typeof entityConf.color_above === 'undefined' ? 'var(--paper-item-icon-color)' : entityConf.color_above;
finalColor = state > colorLimit ? colorAbove : colorBelow;
}
this.sections = [];
filteredConfig.sections.forEach(section => {
let total = 0;
const boxes: Box[] = section.entities
.filter(entityConf => {
const { min_state } = this.config;
// remove empty entity boxes
if (entityConf.type === 'remaining_parent_state') {
return this.connectionsByChild.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
if (entityConf.type === 'remaining_child_state') {
return this.connectionsByParent.get(entityConf)?.some(c => c.state && c.state >= min_state);
}
const { state } = this._getMemoizedState(entityConf);
return state && state >= min_state;
})
.map(entityConf => {
const { state, unit_of_measurement } = this._getMemoizedState(entityConf);
total += state;

let finalColor = entityConf.color || 'var(--primary-color)';
if (typeof entityConf.color_on_state != 'undefined' && entityConf.color_on_state) {
const colorLimit = typeof entityConf.color_limit === 'undefined' ? 1 : entityConf.color_limit;
const colorBelow =
typeof entityConf.color_below === 'undefined' ? 'var(--primary-color)' : entityConf.color_below;
const colorAbove =
typeof entityConf.color_above === 'undefined' ? 'var(--paper-item-icon-color)' : entityConf.color_above;
finalColor = state > colorLimit ? colorAbove : colorBelow;
}

return {
config: entityConf,
entity: this._getEntityState(entityConf),
entity_id: getEntityId(entityConf),
state,
unit_of_measurement,
color: finalColor,
children: entityConf.children,
connections: { parents: [] },
top: 0,
size: 0,
};
});
if (!boxes.length) {
return {
boxes,
total,
spacerH: 0,
statePerPixelY: 0,
config: entityConf,
entity: this._getEntityState(entityConf),
entity_id: getEntityId(entityConf),
state,
unit_of_measurement,
color: finalColor,
children: entityConf.children,
connections: { parents: [] },
top: 0,
size: 0,
};
}
// leave room for margin
const availableHeight = this.config.height - (boxes.length - 1) * this.config.min_box_distance;
// calc sizes to determine statePerPixelY ratio and find the best one
const calcResults = this._calcBoxHeights(boxes, availableHeight, total);
return {
boxes: this._sortBoxes(calcResults.boxes, section.sort_by, section.sort_dir),
total,
statePerPixelY: calcResults.statePerPixelY,
};
})
.filter(s => s.boxes.length > 0)
.map(section => {
// calc sizes again with the best statePerPixelY
let totalSize = 0;
let { boxes } = section;
if (section.statePerPixelY !== this.statePerPixelY) {
boxes = boxes.map(box => {
const size = Math.max(this.config.min_box_height, Math.floor(box.state / this.statePerPixelY));
totalSize += size;
return {
...box,
size,
};
});
} else {
totalSize = boxes.reduce((sum, b) => sum + b.size, 0);
}
// calc vertical margin size
const extraSpace = this.config.height - totalSize;
const spacerH = boxes.length > 1 ? extraSpace / (boxes.length - 1) : this.config.height;
let offset = 0;
// calc y positions. needed for connectors
boxes = boxes.map(box => {
const top = offset;
offset += box.size + spacerH;
});
if (!boxes.length) {
return;
}
// leave room for margin
const availableHeight = this.config.height - (boxes.length - 1) * this.config.min_box_distance;
// calc sizes to determine statePerPixelY ratio and find the best one
const calcResults = this._calcBoxHeights(boxes, availableHeight, total);
const parentBoxes = this.sections[this.sections.length - 1]?.boxes || [];
const sectionState = {
boxes: this._sortBoxes(parentBoxes, calcResults.boxes, section.sort_by, section.sort_dir),
total,
statePerPixelY: calcResults.statePerPixelY,
};

// calc sizes again with the best statePerPixelY
let totalSize = 0;
let sizedBoxes = sectionState.boxes;
if (sectionState.statePerPixelY !== this.statePerPixelY) {
sizedBoxes = sizedBoxes.map(box => {
const size = Math.max(this.config.min_box_height, Math.floor(box.state / this.statePerPixelY));
totalSize += size;
return {
...box,
top,
size,
};
});
} else {
totalSize = sizedBoxes.reduce((sum, b) => sum + b.size, 0);
}
// calc vertical margin size
const extraSpace = this.config.height - totalSize;
const spacerH = sizedBoxes.length > 1 ? extraSpace / (sizedBoxes.length - 1) : this.config.height;
let offset = 0;
// calc y positions. needed for connectors
sizedBoxes = sizedBoxes.map(box => {
const top = offset;
offset += box.size + spacerH;
return {
...section,
boxes,
spacerH,
...box,
top,
};
});
this.sections.push({
...sectionState,
boxes: sizedBoxes,
spacerH,
});
});
}

private _sortBoxes(boxes: Box[], sort?: string, dir = 'desc') {
private _sortBoxes(parentBoxes: Box[], boxes: Box[], sort?: string, dir = 'desc') {
if (sort === 'state') {
const sortByParent = (a: Box, b: Box, realSort: (a: Box, b: Box) => number) => {
const parentIndexA = parentBoxes.findIndex(p => p.children.includes(a.entity_id));
const parentIndexB = parentBoxes.findIndex(p => p.children.includes(b.entity_id));
return parentIndexA < parentIndexB ? -1 : parentIndexA > parentIndexB ? 1 : realSort(a, b);
};

if (dir === 'desc') {
boxes.sort((a, b) => (a.state > b.state ? -1 : a.state < b.state ? 1 : 0));
boxes.sort((a, b) => sortByParent(a, b, (a, b) => (a.state > b.state ? -1 : a.state < b.state ? 1 : 0)));
} else {
boxes.sort((a, b) => (a.state < b.state ? -1 : a.state > b.state ? 1 : 0));
boxes.sort((a, b) => sortByParent(a, b, (a, b) => (a.state < b.state ? -1 : a.state > b.state ? 1 : 0)));
}
}
return boxes;
Expand Down Expand Up @@ -477,7 +482,7 @@ export class Chart extends LitElement {
const maxLabelH = box.size + spacerH - 1;
// reduce label size if it doesn't fit
const labelStyle: Record<string, string> = {lineHeight: MIN_LABEL_HEIGHT + 'px'};
const labelStyle: Record<string, string> = { lineHeight: MIN_LABEL_HEIGHT + 'px' };
const nameStyle: Record<string, string> = {};
if (maxLabelH < MIN_LABEL_HEIGHT) {
const fontSize = maxLabelH / MIN_LABEL_HEIGHT;
Expand All @@ -492,11 +497,11 @@ export class Chart extends LitElement {
nameStyle.fontSize = `${1 / numLines + 0.1}rem`;
nameStyle.lineHeight = `${1 / numLines + 0.1}rem`;
} else if (maxLabelH < MIN_LABEL_HEIGHT * numLines) {
nameStyle.fontSize = `${maxLabelH / MIN_LABEL_HEIGHT / numLines * 1.1}em`;
nameStyle.lineHeight = `${maxLabelH / MIN_LABEL_HEIGHT / numLines * 1.1}em`;
nameStyle.fontSize = `${(maxLabelH / MIN_LABEL_HEIGHT / numLines) * 1.1}em`;
nameStyle.lineHeight = `${(maxLabelH / MIN_LABEL_HEIGHT / numLines) * 1.1}em`;
}
}
return html`
${i > 0 ? html`<div class="spacerv" style=${styleMap({ height: spacerH + 'px' })}></div>` : null}
${extraSpacers
Expand Down Expand Up @@ -626,4 +631,4 @@ export class Chart extends LitElement {
}
}

export default Chart;
export default Chart;

0 comments on commit 73edbdb

Please sign in to comment.