You can use these expressions in the Chapter 13 ThisIsHtml column to render the current distribution of Car objects by brand as HTML or SVG; this page is for Bootcamp learners completing Training:Bootcamp:Chapter 13.
Before you paste a snippet
Complete the setup through the relevant step in Training:Bootcamp:Chapter 13 before using these expressions.
- Create the
CarsAndBrandsViewModel forCar. - Add the
ThisIsHtmlgeneric column. - Set the column tagged value
DataIsHtmltoTrue. This allows the generated string to render as HTML rather than appear as text. - Verify the basic rendering test first. For example, the following expression should display each registration number in a yellow block:
Car.allinstances->collect(c|
'<div style="background:yellow;">'+c.RegistrationNumber+'</div>'
)->asSeparatedList( '' )The chart expressions below use OCL collection operations to create markup:
| Expression element | What it does in this exercise |
|---|---|
Car.allInstances
|
Gets the cars used as the chart data. |
| c.BrandOfCar) | Creates one group for each car brand. |
tuple.List->size
|
Gets the number of cars in that brand's group. |
(100*.../cars->size).Round(0).asstring+'%'
|
Produces a whole-number percentage such as 25%.
|
collect(...)->asSeparatedList( )
|
Produces one markup string by joining the generated bars with no separator. |
Use single quotes for OCL string constants. The embedded HTML and CSS use double quotes where appropriate, which avoids escaping single quotes in attributes.
Step 396 and 399: HTML bar chart
Paste the following complete expression into the Expression box for ThisIsHtml. It generates one horizontal bar per BrandOfCar; the bar width is that brand's percentage of all cars.
'<style>
.chart-wrap {
margin-left: 50px;
font-family: sans-serif;
height: 650px;
width: 300px;
}
.chart-wrap .title {
font-weight: bold;
font-size: 1.62em;
padding: 0.5em 0 1.8em 0;
text-align: center;
white-space: nowrap;
}
.chart-wrap.vertical .grid {
transform: translateY(-175px) translateX(175px) rotate(-90deg);
}
.chart-wrap.vertical .grid .bar::after {
transform: translateY(-50%) rotate(45deg);
display: block;
}
.chart-wrap.vertical .grid::before,
.chart-wrap.vertical .grid::after {
transform: translateX(-0.2em) rotate(90deg);
}
.chart-wrap .grid {
position: relative;
padding: 5px 0 5px 0;
height: 100%;
width: 100%;
border-left: 2px solid #aaa;
background: repeating-linear-gradient(90deg,transparent,transparent 19.5%,rgba(170,170,170,0.7) 20%);
}
.chart-wrap .grid::before {
font-size: 0.8em;
font-weight: bold;
content: "0%";
position: absolute;
left: -0.5em;
top: -1.5em;
}
.chart-wrap .grid::after {
font-size: 0.8em;
font-weight: bold;
content: "100%";
position: absolute;
right: -1.5em;
top: -1.5em;
}
.chart-wrap .bar {
width: var(--bar-value);
height: 50px;
margin: 30px 0;
background-color: #F16335;
border-radius: 0 3px 3px 0;
}
.chart-wrap .bar:hover {
opacity: 0.7;
}
.chart-wrap .bar::after {
content: attr(data-name);
margin-left: 100%;
padding: 10px;
display: inline-block;
white-space: nowrap;
}
</style>
'+
'
<div class="chart-wrap vertical">
<h2 class="title">Bar Chart for Cars and Brands</h2>
<div class="grid">
'+
Car.allInstances->groupby(c|c.BrandOfCar)->collect(tuple|tuple.BrandOfCar.Name,(100*tuple.List->size/Car.allinstances->size).Round(0).asstring+'%')
->collect(values|'<div class="bar" style="--bar-value:'+values.Part2+';" data-name="'+values.Name+'" title="'+values.Name+' '+values.Part2+'"></div>'
)
->asSeparatedList( '' )+
'
</div>
</div>'What the generated markup looks like
For a brand named Volvo representing 25% of the cars, the inner collection produces a bar equivalent to:
<div class="bar" style="--bar-value:25%;" data-name="Volvo" title="Volvo 25%"></div>
The CSS reads --bar-value as the bar width and reads data-name to display the label.
Verify the HTML chart
- Save the ViewModel and open the web application.
- Confirm that each brand has a bar and that its label and percentage are displayed.
- Open a second window, create a car with a new brand, and return to the chart. The new brand must appear in the chart data.
- When the chart works, remove the helper columns as described in step 401: delete
AllCar, deleteCar, resizeThisIsHtml, remove<Name>from the Presentation box, and setHideSidebaronCarsAndBrands.
Step 406: SVG vertical bar chart
Make a copy of the HTML ViewModel named CarsAndBrandsSVG, add the global action for that ViewModel, and first confirm that the static SVG example renders, as directed in Training:Bootcamp:Chapter 13. Then replace the ThisIsHtml expression with the following dynamic SVG expression.
'<style>
h1 {
font: 24px sans-serif;
}
.bar {
fill: #d81c3f;
}
.bar:hover {
fill: darkgray;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
'+
'
<div class="chart-wrap vertical">
<h2 class="title">Bar Chart for Cars and Brands</h2>
<div class="grid">
'+
Car.allInstances->groupby(c|c.BrandOfCar)->collect(tuple|tuple.BrandOfCar.Name,(100*tuple.List->size/Car.allinstances->size).Round(0).asstring+'%')
->collect(values|'<div class="bar" style="--bar-value:'+values.Part2+';" data-name="'+values.Name+'" title="'+values.Name+' '+values.Part2+'"></div>'
)
->asSeparatedList( '' )+
'
</div>
</div>'+
'<h1>SVG Vertical Bar Chart</h1>
<svg width="960" height="500">
<g transform="translate(40,20)">
<g class="x axis" transform="translate(0,450)">'+
let cars=Car.allInstances in
(
cars->groupby(c|c.BrandOfCar)->collect(tuple|tuple.BrandOfCar.Name,(100*tuple.List->size/cars->size).Round(0).asstring+'%',BrandOfCar.allinstances->indexof(tuple.BrandOfCar)*35)
->collect(values|'
<g class="tick" transform="translate('+values.Part3.asstring+',0)" style="opacity: 1;"><line y2="6" x2="0"></line>
<text dy=".71em" y="9" x="0" style="text-anchor: middle;">'+values.Name+'</text>
</g>
'
)
->asSeparatedList( '' )
)+
'
<path class="domain" d="M0,6V0H900V6"></path>
</g>
<g class="y axis">
<g class="tick" transform="translate(0,450)" style="opacity: 1;"><line x2="-6" y2="0"></line>
<text dy=".32em" x="-9" y="0" style="text-anchor: end;">0%</text>
</g>
<g class="tick" transform="translate(0,212)" style="opacity: 1;"><line x2="-6" y2="0"></line>
<text dy=".32em" x="-9" y="0" style="text-anchor: end;">50%</text>
</g>
<g class="tick" transform="translate(0,24.87009919697684)" style="opacity: 1;"><line x2="-6" y2="0"></line>
<text dy=".32em" x="-9" y="0" style="text-anchor: end;">100%</text>
</g>
<path class="domain" d="M-6,0H0V450H-6"></path>
<text transform="rotate(-90)" y="6" dy=".71em" style="text-anchor: end;">Frequency</text>
</g>
'+
let cars=Car.allInstances in
(
cars->groupby(c|c.BrandOfCar)->collect(tuple|tuple.BrandOfCar.Name,
(450*tuple.List->size/cars->size).Round(0),
BrandOfCar.allinstances->indexof(tuple.BrandOfCar)*35)
->collect(values|'
<rect class="bar" x="'+values.Part3.asstring+'" width="31" y="'+values.Part2.asstring+'" height="'+(450-values.Part2).asstring+'"></rect>
'
)
->asSeparatedList( '' )
)+
'
</svg>'How the SVG values are calculated
The SVG plot area is 450 units high.
- The first SVG collection calculates a percentage label and an x-position. The x-position is the index of the brand multiplied by
35. - The second SVG collection calculates a bar value on the 450-unit scale:
450*tuple.List->size/cars->size. - Each generated
rectuses that value foryand uses450 - valueforheight.
For example, if a brand represents 20% of all cars, its calculated bar value is 90. The generated rectangle has y="90" and height="360".
= Diagnose a chart that does not render
- Confirm that
DataIsHtmlremains set toTrueforThisIsHtml. - Test the static HTML or SVG before adding OCL-generated values. This separates markup and CSS problems from data-expression problems.
- Use the Chrome Debugger to inspect what the browser received. Check whether generated
rectelements and their numericx,y, andheightattributes are present. - Check the generated data with a known set of cars. A new car under a new brand should add a new label and bar.
- If the ViewModel reports a type error after changing the grouping expression, follow step 398 in Training:Bootcamp:Chapter 13: set the
Carcolumn class toDerive type from referring column, then use the adjusted expressions for the grouped values.
