Add page to analyze differences in the distribution of users in different channels
This commit is contained in:
Родитель
74556e1dd2
Коммит
eb0bdff46d
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Analyze distribution</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js" integrity="sha256-dsOXGNHAo/syFnazt+KTBsCQeRmlcW1XKL0bCK4Baec=" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/js/standalone/selectize.js" integrity="sha256-YhmtrYJDSZgZFL8jDCR8VNOV/iigJPZYNCk8L9J72Hk=" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/css/selectize.css" integrity="sha256-HzWsbetzuScwBVnRYZIRJeXPQjHvyAMWhukery/8L8A=" crossorigin="anonymous" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="correlations.js" type="text/javascript"></script>
|
||||
<script src="channels_diff.js" type="text/javascript"></script>
|
||||
<select name="property" id="property"></select>
|
||||
<select name="value" id="value"></select>
|
||||
<table id="table">
|
||||
<tr>
|
||||
<th>Percentage in nightly</th>
|
||||
<th>Percentage in aurora</th>
|
||||
<th>Percentage in beta</th>
|
||||
<th>Percentage in release</th>
|
||||
</tr>
|
||||
</table>
|
||||
<svg id="image" width="1200" height="900"></svg>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,212 @@
|
|||
let options = {
|
||||
'property': {
|
||||
value: null,
|
||||
type: 'option',
|
||||
},
|
||||
'value': {
|
||||
value: null,
|
||||
type: 'option',
|
||||
},
|
||||
};
|
||||
|
||||
function getOption(name) {
|
||||
return options[name].value;
|
||||
}
|
||||
|
||||
function getOptionType(name) {
|
||||
return options[name].type;
|
||||
}
|
||||
|
||||
function setOption(name, value) {
|
||||
return options[name].value = value;
|
||||
}
|
||||
|
||||
let onLoad = new Promise(function(resolve, reject) {
|
||||
window.onload = resolve;
|
||||
});
|
||||
|
||||
function setURL() {
|
||||
let url = new URL(location.href);
|
||||
|
||||
let property = getOption('property');
|
||||
let value = getOption('value');
|
||||
if (!property) {
|
||||
url.search = '';
|
||||
} else if (!value) {
|
||||
url.search = '?property=' + property;
|
||||
} else {
|
||||
url.search = '?property=' + property + '&value=' + value;
|
||||
}
|
||||
|
||||
history.replaceState({}, document.title, url.href);
|
||||
}
|
||||
|
||||
function populateTable() {
|
||||
let property = getOption('property');
|
||||
let value = getOption('value');
|
||||
if (!property || !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let channels = ['nightly', 'aurora', 'beta', 'release'];
|
||||
|
||||
let results = {};
|
||||
|
||||
Promise.all(
|
||||
channels
|
||||
.map((channel, index) =>
|
||||
correlations.getChannelsPercentage('Firefox', channel, property, value)
|
||||
.then(result => results[channel] = result)
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
let table = document.getElementById('table');
|
||||
while (table.rows.length > 1) {
|
||||
table.deleteRow(table.rows.length - 1);
|
||||
}
|
||||
|
||||
let row = table.insertRow(table.rows.length);
|
||||
|
||||
channels.forEach((channel, index) => {
|
||||
let cell = row.insertCell(index);
|
||||
cell.appendChild(document.createTextNode(correlations.toPercentage(results[channel] && results[channel].p || 0) + ' %'));
|
||||
});
|
||||
|
||||
let svgElem = document.getElementById('image');
|
||||
|
||||
d3.select(svgElem).selectAll('*').remove();
|
||||
|
||||
let margin = { top: 20, right: 300, bottom: 70, left: 300 };
|
||||
let width = svgElem.getAttribute('width') - margin.left - margin.right;
|
||||
let height = svgElem.getAttribute('height') - margin.top - margin.bottom;
|
||||
|
||||
let x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
|
||||
let y = d3.scale.linear().range([height, 0]);
|
||||
|
||||
let color = d3.scale.ordinal()
|
||||
.range(['black', 'blue', 'red', 'orange']);
|
||||
|
||||
let xAxis = d3.svg.axis()
|
||||
.scale(x)
|
||||
.orient("bottom");
|
||||
|
||||
let yAxis = d3.svg.axis()
|
||||
.scale(y)
|
||||
.orient("left")
|
||||
.ticks(10);
|
||||
|
||||
var svg = d3.select(svgElem)
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
let data = channels.map(channel => {
|
||||
return {
|
||||
channel: channel,
|
||||
value: (results[channel] && results[channel].p || 0) * 100,
|
||||
};
|
||||
});
|
||||
|
||||
x.domain(channels);
|
||||
y.domain([0, d3.max(data, function(d) { return d.value; })]);
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(27," + height + ")")
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "end")
|
||||
.attr("dx", "-.8em")
|
||||
.attr("dy", "-.55em")
|
||||
.attr("transform", "rotate(-90)" );
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "y axis")
|
||||
.call(yAxis)
|
||||
.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", 3)
|
||||
.attr("dy", ".71em")
|
||||
.style("text-anchor", "end")
|
||||
.text("Percentage of users (%)");
|
||||
|
||||
svg.selectAll("bar")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.style('fill', d => color(d.channel))
|
||||
.attr("x", function(d) { return 27 + x(d.channel); })
|
||||
.attr("width", x.rangeBand())
|
||||
.attr("y", function(d) { return y(d.value); })
|
||||
.attr("height", function(d) { return height - y(d.value); });
|
||||
});
|
||||
}
|
||||
|
||||
function populateValueSelect() {
|
||||
let property = getOption('property');
|
||||
if (!property) {
|
||||
return;
|
||||
}
|
||||
|
||||
correlations.getChannelsValues('Firefox', property)
|
||||
.then(values => {
|
||||
if ($('#value')[0].selectize) {
|
||||
$('#value')[0].selectize.destroy();
|
||||
}
|
||||
|
||||
let $select = $('#value').selectize({
|
||||
valueField: 'name',
|
||||
labelField: 'name',
|
||||
searchField: 'name',
|
||||
options: values.map(value => {
|
||||
return {'name': value};
|
||||
}),
|
||||
onChange: value => {
|
||||
setOption('value', value);
|
||||
setURL();
|
||||
populateTable();
|
||||
},
|
||||
});
|
||||
|
||||
$select[0].selectize.setValue(getOption('value'));
|
||||
});
|
||||
}
|
||||
|
||||
onLoad
|
||||
.then(() => correlations.getChannelsProperties('Firefox'))
|
||||
.then(props => {
|
||||
let queryVars = new URL(location.href).search.substring(1).split('&');
|
||||
|
||||
Object.keys(options)
|
||||
.forEach(function(optionName) {
|
||||
for (let queryVar of queryVars) {
|
||||
if (queryVar.startsWith(optionName + '=')) {
|
||||
let option = queryVar.substring((optionName + '=').length).trim();
|
||||
setOption(optionName, decodeURIComponent(option));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let $select = $('#property').selectize({
|
||||
valueField: 'name',
|
||||
labelField: 'name',
|
||||
searchField: 'name',
|
||||
options: props.map(prop => {
|
||||
return {'name': prop};
|
||||
}),
|
||||
onChange: prop => {
|
||||
if (prop != getOption('property')) {
|
||||
setOption('value', '');
|
||||
}
|
||||
setOption('property', prop);
|
||||
setURL();
|
||||
populateValueSelect();
|
||||
},
|
||||
});
|
||||
|
||||
$select[0].selectize.setValue(getOption('property'));
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
});
|
|
@ -359,6 +359,27 @@ var correlations = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function getChannelsProperties(product) {
|
||||
return loadChannelsDifferencesData(product)
|
||||
.then(() => ['release', 'beta', 'aurora', 'nightly'].map(channel => channelsData[channel].map(longitudinalElem => Object.keys(longitudinalElem.item)[0])))
|
||||
.then(all_props_per_channel => [].concat.apply([], all_props_per_channel))
|
||||
.then(all_props => new Set(all_props))
|
||||
.then(props => Array.from(props));
|
||||
}
|
||||
|
||||
function getChannelsValues(product, property) {
|
||||
return loadChannelsDifferencesData(product)
|
||||
.then(() => ['release', 'beta', 'aurora', 'nightly'].map(channel => channelsData[channel].filter(longitudinalElem => Object.keys(longitudinalElem.item).indexOf(property) != -1).map(longitudinalElem => longitudinalElem.item[property])))
|
||||
.then(all_values_per_channel => [].concat.apply([], all_values_per_channel))
|
||||
.then(all_values => new Set(all_values))
|
||||
.then(values => Array.from(values));
|
||||
}
|
||||
|
||||
function getChannelsPercentage(product, channel, property, value) {
|
||||
return loadChannelsDifferencesData(product)
|
||||
.then(() => channelsData[channel].find(longitudinalElem => Object.keys(longitudinalElem.item).indexOf(property) != -1 && longitudinalElem.item[property] == value));
|
||||
}
|
||||
|
||||
function text(textElem, signature, channel, product, show_ci=false) {
|
||||
loadCorrelationData(signature, channel, product)
|
||||
.then(data => {
|
||||
|
@ -522,6 +543,9 @@ var correlations = (() => {
|
|||
getAnalysisDate: getAnalysisDate,
|
||||
text: text,
|
||||
rerank: rerank,
|
||||
getChannelsProperties: getChannelsProperties,
|
||||
getChannelsValues: getChannelsValues,
|
||||
getChannelsPercentage: getChannelsPercentage,
|
||||
toPercentage: toPercentage,
|
||||
graph: graph,
|
||||
};
|
||||
|
|
|
@ -354,6 +354,7 @@ if __name__ == "__main__":
|
|||
'supergraph.html', 'supergraph.js',
|
||||
'rerank.html', 'rerank.js',
|
||||
'common_landings.html', 'common_landings.js',
|
||||
'channels_diff.html', 'channels_diff.js',
|
||||
] + ['images/' + image for image in os.listdir('images')]
|
||||
|
||||
for f in files:
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 23 KiB |
10
index.html
10
index.html
|
@ -30,13 +30,15 @@ td {
|
|||
</tr>
|
||||
<tr>
|
||||
<td><a href="buildid_changeset.html">Build ID <-> Changeset conversion</a></td>
|
||||
<td><img src="images/buildid_changeset.png" alt="Build ID <-> Changeset conversion" width="192" height="16" /></td>
|
||||
<td><img src="images/buildid_changeset.png" alt="Build ID <-> Changeset conversion" width="192" height="32" /></td>
|
||||
<td><a href="common_landings.html">Find common landings between channels</a></td>
|
||||
<td><img src="images/common_landings.png" alt="Find common landings between channels" width="192" height="96" /></td>
|
||||
<td><a href="addon_related_signatures.html">List of addon-related signatures</a></td>
|
||||
<td><img src="images/addon_related_signatures.png" alt="List of addon-related signatures" width="192" height="96" /></td>
|
||||
<td><img src="images/common_landings.png" alt="Find common landings between channels" width="192" height="64" /></td>
|
||||
<td><a href="channels_diff.html">Analyze distribution differences between channels</a></td>
|
||||
<td><img src="images/channels_diff.png" alt="Analyze distribution differences between channels" width="192" height="96" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="addon_related_signatures.html">List of addon-related signatures</a></td>
|
||||
<td><img src="images/addon_related_signatures.png" alt="List of addon-related signatures" width="192" height="96" /></td>
|
||||
<td><a href="graphics_critical_errors.html">List of graphics critical errors</a></td>
|
||||
<td><img src="images/graphics_critical_errors.png" alt="List of graphics critical errors" width="192" height="96" /></td>
|
||||
</tr>
|
||||
|
|
Загрузка…
Ссылка в новой задаче