<figure class="css-chart" style="--widget-size: 200px;">
<ul class="line-chart">
<li style="--x: 40px; --y: 83.3333px;">
<div class="data-point" data-value="25"></div>
<div class="line-segment" style="--hypotenuse: 123.33333333333333; --angle:-71.07535558394876;"></div>
</li>
<li style="--x: 80px; --y: 200px;">
<div class="data-point" data-value="60"></div>
<div class="line-segment" style="--hypotenuse: 64.03124237432849; --angle:51.34019174590991;"></div>
</li>
<li style="--x: 120px; --y: 150px;">
<div class="data-point" data-value="45"></div>
<div class="line-segment" style="--hypotenuse: 43.333333333333336; --angle:-22.61986494804045;"></div>
</li>
<li style="--x: 160px; --y: 166.667px;">
<div class="data-point" data-value="50"></div>
<div class="line-segment" style="--hypotenuse: 52.068331172711055; --angle:39.805571092265225;"></div>
</li>
<li style="--x: 200px; --y: 133.333px;">
<div class="data-point" data-value="40"></div>
<div class="line-segment" style="--hypotenuse: 0; --angle:0;"></div>
</li>
</ul>
</figure>
<figure class="css-chart" style="--widget-size: 200px;">
<ul id="line-chart" class="line-chart">
</ul>
</figure>
.css-chart {
border-bottom: 1px solid;
border-left: 1px solid;
display: inline-block;
height: var(--widget-size);
margin: 5em 15em 1em 5em;
padding: 0;
position: relative;
width: var(--widget-size);
}
.line-chart {
list-style: none;
margin: 0;
padding: 0;
}
.data-point {
background-color: white;
border: 2px solid lightblue;
border-radius: 50%;
bottom: calc(var(--y) - 8px);
height: 12px;
left: calc(var(--x) - 8px);
position: absolute;
width: 12px;
z-index: 1;
}
.line-segment {
background-color: blue;
bottom: var(--y);
height: 3px;
left: var(--x);
position: absolute;
transform: rotate(calc(var(--angle) * 1deg));
transform-origin: left bottom;
width: calc(var(--hypotenuse) * 1px);
}
const chartValues = [{value: 25},{value: 60},{value: 45},{value: 50},{value: 40}]
function formatLineChartData(values, chartHeight) {
//divide chart size by total number of points to get length of triangle base. That becomes the left offset for each new point
//subtract previous point height from new point height to get the rise of the triangle. That becomes the bottom offset for the new point.
//use base squared + rise squared to find the length of the hypotenuse. That becomes the width of the line to draw.
//use Math.asin(base / hypotenuse) [then convert the radians to degrees] to find the degree angle to rotate the line to.
//Multiply the rotation angle by -1 if it needs to rise to meet the next point.
const widgetSize = chartHeight;
const pointSize = 16;
const base = (widgetSize - pointSize / 2 ) / values.length;
let sortedValues = sortValues([...values]);
const topMostPoint = sortedValues[0].value;
let leftOffset = pointSize; //padding for left axis labels
let nextPoint = 0;
let rise = 0;
let cssValues = [];
for (var i=0, len=values.length-1; i<len; i++) {
var currentValue = {
left: 0,
bottom: 0,
hypotenuse: 0,
angle: 0,
value: 0
};
currentValue.value = values[i].value;
currentValue.left = leftOffset;
leftOffset += base;
currentValue.bottom = (widgetSize - pointSize) * (currentValue.value / topMostPoint);
nextPoint = (widgetSize - pointSize) * (values[i+1].value / topMostPoint);
rise = currentValue.bottom - nextPoint;
currentValue.hypotenuse = Math.sqrt((base * base) + (rise * rise));
currentValue.angle = radiansToDegrees(Math.asin(rise / currentValue.hypotenuse));
cssValues.push(currentValue);
}
var lastPoint = {
left: leftOffset,
bottom: (widgetSize - pointSize) * (values[values.length - 1].value / topMostPoint),
hypotenuse: 0,
angle: 0,
value: values[values.length - 1].value
};
cssValues.push(lastPoint);
return cssValues;
}
const sortValues = values => values.sort((a, b) => b.value - a.value)
const radiansToDegrees = (rads) => rads * (180 / Math.PI)
const sum = (total, value) => total + value.value
function render(data, container) {
data.forEach((item) => {
let markup = createListItem(item);
let listItem = document.createElement("li");
listItem.style.cssText = `--x: ${item.left}px; --y: ${item.bottom}px`;
listItem.innerHTML = markup;
container.appendChild(listItem);
});
}
function createListItem(item) {
return `
<div class="data-point" data-value="${item.value}"></div>
<div class="line-segment" style="--hypotenuse: ${item.hypotenuse}; --angle:${item.angle};"></div>
`;
}
render(formatLineChartData(chartValues, 200), document.getElementById('line-chart'))
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.