Skip to main content
Question

Age pyramid - custom more than one color and data for each sex

  • October 23, 2025
  • 1 reply
  • 15 views

Hello, 

I want to create a new age pyramid, but where we distinguish for example nationality (CH and foreign) among men and women. This means I need to add to sets of data and distinguish with two colors. I tried to follow a similar example given in the Widget Tricks Age Pyramid with custom HTML/CSS – Codelibrary, but I have no clue how to solve this. 

This is what it should look like in the end :

If you have already tried this of have an idea, I would be really thankful. 

All the best, 

Eric

1 reply

frederic.passaniti
Huwise Team

Hi, sure,

You can add another field in the group_by to add another dimension for the analysis. 

 

 

Here is the code library pyramid with 3 dimensions :

https://fpassaniti.opendatasoft.com/pages/test-pyramide-a-3-dimensions/

 

<ods-dataset-context
context="ctx"
ctx-dataset="north-carolina-population-overview-2010"
ctx-domain="userclub">
<div ng-init="variables = {'index' : {}, 'max' : {}, 'displayvalues' : false};
agefield = 'age';
genderfield = 'sex';
racefield = 'race';
valuefield = 'value'">
<div class="row">
<h3>Age pyramid for the state of North Carolina, USA (by Gender and Race)</h3>
<div ods-adv-analysis="analysis"
ods-adv-analysis-context="ctx"
ods-adv-analysis-select="sum({{valuefield}}) as pyramidnumber"
ods-adv-analysis-group-by="{{genderfield}}, {{agefield}}, {{racefield}}"
ods-adv-analysis-order-by="{{agefield}}">
<div ods-adv-analysis="totalbygender"
ods-adv-analysis-context="ctx"
ods-adv-analysis-group-by="{{genderfield}}"
ods-adv-analysis-select="SUM({{valuefield}}) as totalgender"
ods-adv-analysis-order-by="{{genderfield}}">
<div ods-subaggregation="analysis"
ods-subaggregation-serie-maxcount="MAX(pyramidnumber)">
{{ variables.max['pyramidnumber'] = results[0].maxcount ; "" }}
<div ng-repeat="result in analysis">
<div
ng-init="variables.index[result[agefield]] = variables.index[result[agefield]] || {};
variables.index[result[agefield]][result[genderfield]] = variables.index[result[agefield]][result[genderfield]] || {};
variables.index[result[agefield]][result[genderfield]][result[racefield]] = result.pyramidnumber">
</div>
</div>
</div>
<!--- the pyramid --->
<div class="pyramid-line">
<!--- left side : title and total --->
<div class="pyramid-side pyramid-side-left">
{{totalbygender[0][genderfield]}} (total : {{ totalbygender[0].totalgender | number }})
</div>
<!-- middle : title --->
<div class="pyramid-middle">Age</div>
<!--- right side : title and total --->
<div class="pyramid-side pyramid-side-right">
{{totalbygender[1][genderfield]}} (total : {{ totalbygender[1].totalgender | number }})
</div>
</div>

<!-- Legend -->
<div class="pyramid-legend">
<div class="legend-item">
<div class="legend-box white"></div>
<span>White</span>
</div>
<div class="legend-item">
<div class="legend-box other"></div>
<span>Other</span>
</div>
</div>

<div class="pyramid-line" ng-repeat="i in variables.index|keys">
<!--- left side : bars --->
<div class="pyramid-side pyramid-side-left">
<div class="fond_bar" style="width: 100%">
<div class="bars-stacked">
<div
class="bar bar-white"
style="width : {{ variables.index[i][totalbygender[0][genderfield]]['White']*100/variables.max['pyramidnumber'] || 0 }}%"
></div>
<div
class="bar bar-other"
style="width : {{ variables.index[i][totalbygender[0][genderfield]]['Other']*100/variables.max['pyramidnumber'] || 0 }}%"
></div>
</div>
</div>
<!--- left values to be displayed when the switch button is on --->
<div class="pyramid-value left">
<span ng-if="variables.displayvalues == true">
W: {{variables.index[i][totalbygender[0][genderfield]]['White'] | number}} |
O: {{variables.index[i][totalbygender[0][genderfield]]['Other'] | number}}
</span>
</div>
</div>
<!--- middle : the different age classes titles --->
<div class="pyramid-middle">{{ i }}</div>
<!--- right side : bars --->
<div class="pyramid-side pyramid-side-right">
<div class="fond_bar" style="width: 100%">
<div class="bars-stacked">
<div
class="bar bar-white"
style="width : {{ variables.index[i][totalbygender[1][genderfield]]['White']*100/variables.max['pyramidnumber'] || 0 }}%"
></div>
<div
class="bar bar-other"
style="width : {{ variables.index[i][totalbygender[1][genderfield]]['Other']*100/variables.max['pyramidnumber'] || 0 }}%"
></div>
</div>
</div>
<!--- right values to be displayed when the switch button is on --->
<div class="pyramid-value right">
<span ng-if="variables.displayvalues == true">
W: {{variables.index[i][totalbygender[1][genderfield]]['White'] | number}} |
O: {{variables.index[i][totalbygender[1][genderfield]]['Other'] | number}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>

<!--- switch button to display values --->
<div class="row">
<div class="col-sm-6">
<div class="pyramid-switch">
<p>Display values:</p>
<label class="switch">
<input
class="switch-input"
type="checkbox"
ng-model="variables.displayvalues"
ng-true-value="true"
ng-false-value="false"
ng-checked="variables.view == true"/>
<div class="switch-button">
<span class="switch-button-left">OFF</span>
<span class="switch-button-right">ON</span>
</div>
</label>
</div>
</div>
<div class="col-sm-6"></div>
</div>
</div>
</ods-dataset-context>
:root {
--left-color: #142E7B;
--right-color: #ffcc00;
--white-opacity: 1;
--other-opacity: 0.6;
}
/***************/
/* Age pyramid */
/***************/
.pyramid-line {
display: flex;
justify-content: space-between;
width: 100%;
margin: auto;
}
.pyramid-middle {
font-weight: 500;
flex: 1;
min-width: 70px;
text-align: center;
white-space: nowrap;
}
.pyramid-side {
flex: 3;
margin: 0px 10px;
text-align: center;
display: flex;
align-items: center;
}
.pyramid-side-left {
color: var(--left-color);
flex-direction: row-reverse;
}
.pyramid-side-right {
color: var(--right-color);
flex-direction: row;
}

/* Stacked bars container */
.bars-stacked {
display: flex;
height: 100%;
border-radius: 1000px;
overflow: hidden;
}

.pyramid-side-left .bars-stacked {
flex-direction: row-reverse;
float: right;
width: 100%;
}

.pyramid-side-right .bars-stacked {
flex-direction: row;
max-width: 100%;
}


.pyramid-side-value {
width: auto;
min-width: 80px;
}
div.fond_bar{
background-color: #f3f3f382;
height: 15px;
border-radius: 1000px;
}
div.bar{
height: 15px;
flex-shrink: 0;
}

/* Bar colors with opacity for race distinction */
.pyramid-side-left div.bar-white {
background-color: var(--left-color);
opacity: var(--white-opacity);
}
.pyramid-side-left div.bar-other {
background-color: var(--left-color);
opacity: var(--other-opacity);
border-radius: 1000px 0 0 1000px;
}
.pyramid-side-right div.bar-white {
background-color: var(--right-color);
opacity: var(--white-opacity);
}
.pyramid-side-right div.bar-other {
background-color: var(--right-color);
opacity: var(--other-opacity);
border-radius: 0 1000px 1000px 0;
}

.pyramid-value {
font-size: 10px;
padding: 0px 5px;
white-space: nowrap;
}
.left {
text-align: start;
}
.right {
text-align: end;
}

/* Legend */
.pyramid-legend {
display: flex;
justify-content: center;
gap: 20px;
margin: 10px 0;
font-size: 14px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-box {
width: 30px;
height: 12px;
border-radius: 1000px;
}
.legend-box.white {
background-color: #999;
opacity: 1;
}
.legend-box.other {
background-color: #999;
opacity: 0.6;
}

/* Optional : SWITCH BUTTON */
.pyramid-switch {
display: flex;
align-items: baseline;
margin-top: 13px;
}
.pyramid-switch .switch {
margin-left: 6px;
}
/* Button Group Switch
========================================================================== */
.switch {
display: inline-block;
margin-bottom: 0.5rem;
}
.switch-button {
/* background and border when in "off" state */
background: var(--highlight);
border: 2px solid var(--highlight);
display: grid;
grid-template-columns: 1fr 1fr;
border-radius: 6px;
color: #FFFFFF;
position: relative;
cursor: pointer;
outline: 0;
user-select: none;
}
.switch-input:not(:checked) + .switch-button .switch-button-left {
/* text color when in "off" state */
color: var(--highlight);
}
.switch-input {
display: none;
}
.switch-button span {
font-size: 1rem;
padding: 0.2rem .7rem;
text-align: center;
z-index: 2;
color: #FFFFFF;
transition: color .2s;
}
.switch-button::before {
content: '';
position: absolute;
background-color: #FFFFFF;
box-shadow: 0 1px 3px rgba(0,0,0,.4);
border-radius: 4px;
top: 0;
left: 0;
height: 100%;
width: 50%;
z-index: 1;
transition: left .3s cubic-bezier(0.175, 0.885, 0.320, 1),
padding .2s ease, margin .2s ease;
}
.switch-button:hover::before {
will-change: padding;
}
.switch-button:active::before {
padding-right: .4rem;
}
/* "On" state
========================== */
.switch-input:checked + .switch-button {
/* background and border when in "on" state */
background-color: var(--links);
border-color: var(--links);
}
.switch-input:checked + .switch-button .switch-button-right {
/* text color when in "on" state */
color: var(--links);
}
.switch-input:checked + .switch-button::before {
left: 50%;
}
.switch-input:checked + .switch-button:active::before {
margin-left: -.4rem;
}
/* Checkbox in disabled state
========================== */
.switch-input[type="checkbox"]:disabled + .switch-button {
opacity: 0.6;
cursor: not-allowed;
}